在阅读Effective Java中的第16条时发现了一个有趣的机制或者说是模式,那就是组合(文中翻译为复用,但是作者认为组合更能体现这种模式的精神),并且文中建议使用组合。
那什么是组合,组合相较于继承的优点在哪里,缺点又有哪些,组合和继承更适合怎样的场景,更重要的是作者在校基础课程的学习中尽然都没有接触到组合这个概念,实在有理由探索一下!
我们分别了解一下组合和继承的概念
组合
- 组合就是 A类的对象是B类的成员变量。相当于 A类对象是B类对象的一个变量,A类中的所有功能,B类都可以通过A类对象的调用来实现。
- 组合体现的是整体与部分、拥有的关系,即 has - a 的关系
让我们用一段代码来演示一下
class 电池{
public void 放电(){
System.out.println("输出电能");
}
}
class 屏幕{
public void 亮屏{
System.out.println("显示内容");
}
}
class CPU{
public void 运算{
System.out.println("浮点计算");
}
}
class 手机{
private 电池 dc;
private 屏幕 pm;
private CPU cpu;
手机(电池 dc,屏幕 pm,CPU cpu){
this.dc = dc;
this.pm = pm;
this.cpu = cpu;
}
public 开机(){
dc.放电();
cpu.运算();
pm.亮屏();
System.out.println("开机成功");
}
public static void main(String[] args) {
电池 dc = new 电池();
屏幕 pm = new 屏幕();
CPU cpu = new CPU();
手机 sj = new 手机(dc,pm,cpu);
sj.开机();
}
}
继承
- 一个类继承另外的一个类的功能,并可以增加它自己的新功能的能力
- 继承体现的是父与子,血缘的关系,即 is - a 的关系
让我们继续放代码来演示
class Animal{
private void beat(){
System.out.println("心脏跳动...");
}
public void breath(){
beat();
System.out.println("呼吸中...");
}
}
class Bird extends Animal{
//创建子类独有的方法fly()
public void fly(){
System.out.println("我是鸟,我在天空中自由的飞翔...");
}
}
class Wolf extends Animal{
//创建子类独有的方法run()
public void run(){
System.out.println("我是狼,我在草原上快速奔跑...");
}
}
public class InheritTest{
public static void main(String[] args){
Bird b=new Bird();
b.breath();
b.fly();
Wolf w=new Wolf();
w.beat();
w.run();
---------- 运行Java程序 ----------
呼吸中...
我是鸟,我在天空中自由的飞翔...
心脏跳动...
我是狼,我在草原上快速奔跑...
}
}
区别和联系
区别
- 组合是一种 has-a 的 关系
- 继承是一种 is-a 的 关系
- 继承结构中,父类的内部细节对于子类是可见的。所以通过继承的代码复用是一种白盒式代码复用。
- 如果父类的实现跟随版本而发生改变,那么子类的实现也将随之改变。这样就导致了子类行为的不可预知性;
- 组合是通过对现有的类进行拼装(组合)产生新的、更复杂的功能。因为在类之间,各自的内部细节是不可见的,所以这种方式的代码复用是黑盒式代码复用。(因为组合中一般都定义一个类型,所以在编译期根本不知道具体会调用哪个实现类的方法)
- 继承,在写代码的时候就要指名具体继承哪个类,所以,在编译期就确定了关系。(从基类继承来的实现是无法在运行期动态改变的,因此降低了应用的灵活性。)
- 组合,在写代码的时候可以采用面向接口编程。所以,类的组合关系一般在运行期确定。
联系
- 组合和继承都是代码复用的一种方式
优点和缺点
在 Effective Java 中非常推荐在需要代码重用的情况下优先使用组合而不是大家常见的继承,让我们们他们各有什么优缺点
- 优点
组合 | 继承 |
---|---|
不破坏封装,整体类与局部类之间松耦合,彼此相对独立 | 子类能自动继承父类的接口 |
具有较好的可扩展性 | 创建子类的对象时,无须创建父类的对象 |
支持动态组合。在运行时,整体对象可以选择不同类型的局部对象 | |
整体类可以对局部类进行包装,封装局部类的接口,提供新的接口 |
- 缺点
组合 | 继承 |
---|---|
整体类不能自动获得和局部类同样的接口 | 子类不能改变父类的接口 |
创建整体类的对象时,需要创建所有局部类的对象 | 不支持动态继承。在运行时,子类无法选择不同的父类 |
空 | 支持扩展,但是往往以增加系统结构的复杂度为代价 |
空 | 破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性 |
可以发现组合相对于继承是一种更好的代码重用手段,但是继承就没有用武之地么,让我们接下来看他们各自的使用场合和情况
不同的场景
- 我们通过最初的组合和继承的示例代码再结合 概念中的 has - a 和 is -a 的描述,能够体会到组合和继承是为了两种场景的重用而设计的
- 当B类真正是A类的 抽象类型时,才适用继承,例如 人 是 婴儿的 抽象类型 是适用于继承的 ,而当 猴子 为 了 重用 人 的呼吸,进食,行动 而 继承于 人 这就是 不适用的.
- 而除去第一种场景,其他场景使用组合是更合适的
- 并且在第一种场景中,使用组合也不是不可以,但继承是最好的方案
结语
文章至此处,想必大家已经明白了组合和继承的关系,并且已经知道在何种情况下使用他们,那么组合VS继承 的 结果就是在更多的场景中使用组合是更好的方案。