首页/技术开发/内容

合成 VS 继承

技术开发2024-12-12 阅读()
[摘要]昨儿看到一片比较 继承与合成 的文章,不错,翻译出来大家共享。内容浅显易懂,相信你也能从中有所收获BTW:翻译的不好不要骂啊,嘿嘿。。。合成 VS 继承关联class的两种基本途径的对比作者:Bil...
昨儿看到一片比较 继承与合成 的文章,不错,翻译出来大家共享。
内容浅显易懂,相信你也能从中有所收获

BTW:翻译的不好不要骂啊,嘿嘿。。。



合成 VS 继承
关联class的两种基本途径的对比
作者:Bill Venners
出处:http://www.artima.com/designtechniques/compoinh.html


摘要
这是我的Design Techniques的一部分,这里我分析了两者的构成(flexibility)和执行牵连(performance implications),并且我针对两者分别给出了指导方针。

正文
建立两个类之间的关联是软件设计的众多基本行为之一。继承和合成是两种基本的实现方法。尽管当你使用继承的时候JVM可以帮你做很多事情,但是你仍然可以使用合成来达到同样的目的。本篇将比较这两种途径且给出使用它们的指导方针。
首先,介绍继承和合成的背景

关于继承
Class Fruit {......}
Class Apple extends Fruit {......}

Apple和Fruit通过extends关联起来,苹果是水果的一种。Fruit是Apple的supperclass,Apple是Fruit的subclass

关于合成
class Fruit {......}

class Apple {
private Fruit fruit = new Fruit();
//......
}

这里Apple和Fruit通过合成关联起来,因为Apple拥有一个引用Fruit对象的实例变量。Apple被称为front-end class,Fruit被称为 back-end class。

动态绑定,多态和改变
当你使用继承来关联两个类的时候,你就可以利用动态绑定和多态的好处了。

动态绑定和多态最主要的好处之一是,他们可以帮助你更简单的修改代码,包括添加新的子类。然而这些不能包括所有你需要改变的地方


修改superclass接口
在继承关联中,superclasses通常被称为“脆弱的(fragile)”,因为对superclass的一点点的改动将波及到众多应用程序的代码。说的更明白些,superclass最脆弱的是它的接口。如果superclass是well-designed的――良好的接口设计,OO风格的实现,那么任何supperclass的实现的改变将不会有任何影响。如果修改superclass的接口,那么将波及到任何使用该superclass的地方和其subclass。
继承有时被成为提供“弱封装(week encapsulation)”,因为你直接使用subclass的地方都会受superclass接口的改变的影响。从某个角度来讲,继承是让subclass重用superclass的代码。


选择合成?
继承性的关联很难来修改superclass的接口。合成则提供了easier-to-change的途径。
通过继承的代码重用
class Fruit {
//返回切割后的份数
public int peel() {
System.out.println(“Peeling is appealing”);
return 1;
}

//将上面替换掉的新方法
public Peel peel() {
return new Peel(1);//另外一个类
}
}

class Apple extends Fruit {
}

class Example1 {
public static void main(String[] args) {
Apple apple = new Apple();
int pieces = apple.peel(); //这里受到影响
}
}

Example1中通过Apple来调用继承自Fruit的peel()方法,但是当我们需要将peel()的返回值从int修改为Peel的时候(上面红色部分),问题出现了,由于类型的不匹配(int peel)造成Example1的代码不能通过编译,虽然Example1并没有和Fruit有任何直接的关联,但还是受到了很大影响。

通过合成的代码重用
合成通过在Apple中保持一个Fruit对象的引用,在Apple中声明一个新的peel方法,内部实现只是简单的调用Fruit的peel方法。

class Fruit {
//返回切割后的份数
public int peel() {
System.out.println(“Peeling is appealing”);
return 1;
}

//需求改变后,将上面方法修改后的新接口
public Peel peel() {
return new Peel(1);//另外一个类
}
}

class Apple {
private Fruit fruit = new Fruit();

public int peel() {
return fruit.peel();

//使用Fruit的新接口,用于取代上面一行代码
Peel peel = fruit.peel();
return peel.getPeelCount();
}
}

class Example2 {
public static void main(String[] args) {
Apple apple = new Apple();
int pieces = apple.peel();//这里不会有任何影响
}
}

在合成实现方式中,subclass变为front-end class,superclass变为back-end class。使用继承,subclass自动继承了superclass的non-private方法;使用合成,front-end class必须在自身的实现中明确的调用back-end class中的相应的方法。这种直接调用有时被称为“推进(forwarding)”或“委派(delegating)”这个方法的调用到back-end对象。

合成比继承提供了更强壮的在代码重用方面的封装,因为back-end class的修改不会波及任何依赖front-end class的代码。例如我们要将Fruit的peel方法返回值修改为Peel(上面红色部分),同时你会看到Apple的peel方法有相应的改变,这时对Fruit接口的修改将不会影响到Example2的代码,

比较合成与继承
几点对比:
back-end class(合成)比superclass(继承)更容易修改接口。就像前面举例说明的那样,back-end class接口的改变必将导致front-end class实现的改变,但不会影响到front-end class的接口,所以依赖front-end class的代码将正常工作。作为对比,superclass接口的修改不仅波及subclass层,也会影响到所有直接使用superclass接口以及使用subclass接口的地方
front-end class(合成)比subclass(继承)更容易修改接口。正像superclass是脆弱(fragile)的,而subclass是坚硬(rigid)的。你不能只改变subclass的接口而不去确认新接口是否和父类型(supertypes)兼容。例如,你不能在subclass中添加一个与superclass方法同样特征但返回值类型不同的新方法。合成则允许你修改front-end class的接口,而不用关心back-class。
合成允许你延迟back-end objects的创建,直到他们被需要的时候才创建,在front-end object的生命期内可以动态的改变back-end objects。对于继承来说,一旦subclass被创建了,你就可以通过subclass object来获取superclass的某些资源了,在subclass生命期内一直保持着superclass的对象,也就是说,subclass object一旦被创建,superclass就已知且不可改变了。
添加subclasses(继承)比添加front-end class(合成)更容易。因为继承伴随多态。如果你的一些代码仅依赖superclass,那么不用任何修改,你就能够使用一个新的subclass。对于合成来说就不可以,除非你使用带有接口的合成(composition with interfaces)。合成和接口的共同使用将提供一个非常强大的设计工具。
同使用subclass中从继承superclass来的方法实现相比,合成中的直接的方法调用委派经常(often)有性能损耗。Often的意思是说,因为性能依赖众多因素,比如JVM优化并执行程序的能力。
对于合成和继承来说,修改任何class的实现都是简单的。实现改变引起的连锁反映被保留在同一个class


在合成和继承中作出选择
怎么作出选择呢?这里有一个指导方针来让我们趋向合成与继承中的其中一个


继承是is-a的关系
主要是说,继承应该只被用在“subclass is-a superclass”的时候。在上面例子中,Apple is-a Fruit,所以我们倾向使用继承。

当你认为已经有一个is-a关系的时候,你需要问自己 一个非常重要的问题,那就是这个“is-a 关系”是否在应用程序或代码生命周期中保持不变的(constant)。举例:当Employee在某个时间段扮演的角色是Person的时候,你可能认为 Employee is-a Person。如果Person被解雇会怎样?如果Person即是Employee又是Supervisor会怎样?这种暂时的is-a关系通常使用合成,而不是继承。


不要仅仅为了得到代码重用就使用继承
如果你的确想重用代码且没有观察到is-a关系,那么使用合成


不要仅仅为了获取多态就使用继承
如果你却是想要多态,但是没有自然的is-a关系,那么使用带有接口的合成(composition with interfaces),这将在下个月介绍。




:


……

相关阅读