关键词:
本文主要说明Java中继承与组合的概念,以及它们之间的联系与区别。首先文章会给出一小段代码示例,用于展示到底什么是继承。然后演示如何通过“组合”来改进这种继承的设计机制。最后总结这两者的应用场景,即到底应该选择继承还是组合。
1、继承
假设我们有一个名为Insect(昆虫)的类,这个类包含两个方法:1)移动move(); 2)攻击attack()。
代码如下:
class Insect { private int size; private String color; public Insect(int size, String color) { this.size = size; this.color = color; } public int getSize() { return size; } public void setSize(int size) { this.size = size; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public void move() { System.out.println("Move"); } public void attack() { move(); //假设昆虫在攻击前必须要先移动一次 System.out.println("Attack"); } }
现在,你想要定义一个名为Bee(蜜蜂)的类。Bee(蜜蜂)是Insect(昆虫)的一种,但实现了不同于Insect(昆虫)的attack()和move方法。这时候我们可以用继承的设计机制来实现Bee类,就像下面的代码一样:
class Bee extends Insect { public Bee(int size, String color) { super(size, color); } public void move() { System.out.println("Fly"); } public void attack() { move(); super.attack(); } }
public class InheritanceVSComposition { public static void main(String[] args) { Insect i = new Bee(1, "red"); i.attack(); } }
InheritanceVSComposition作为一个测试类,在其main方法中生成了一个Bee类的实例,并赋值给Insect类型的引用变量 i。所以调用i的attack方法时,对应的是Bee类实例的attack方法,也就是调用了Bee类的attack方法。
类的继承结构图如下,非常简单:
输出:
Fly
Fly
Attack
Fly被打印了两次,也就是说move方法被调用了两次。但按理来讲,move方法只应当被调用一次,因为无论是昆虫还是蜜蜂,一次攻击前只移动一次。
问题出在子类(即Bee类)的attack方法的重载代码中,也就是super.attack()这一句。因为在父类(即Insect类)中,调用 attack方法时会先调用move方法,所以当子类(Bee)调用super.attack()时,相当于也同时调用了被重载的move方法(注意是子 类被重载的move方法,而不是父类的move方法)。
为了解决这个问题,我们可以采取以下办法:
- 删除子类的attack方法。这么做会使得子类的attack方法的实现完全依赖于父类对于该方法的实现(因为子类继承了父类的attack方法)。如果 父类的attack方法不受控制而产生了变更。比如说,父类的attack方法中调用了另外的move方法,那么子类的attack方法也会产生相应的变 化,这是一种很糟糕的封装。
- 也可以重写子类的attack方法,像下面这样:
public void attack() { move(); System.out.println("Attack"); }
这样保证了结果的正确性,因为子类的attack方法不再依赖于父类。但是,子类attack方法的代码与父类产生了重复(重复的attack方法会使得很多事情变得复杂,不仅仅是多打印了一条输出语句)。所以第二种办法也不行,它不符合软件工程中关于重用的思想。
如此看来,继承机制是有缺点的:子类依赖于父类的实现细节,如果父类产生了变更,子类的后果将不堪设想。
2、组合
在上面的例子中,可以用组合的机制来替代继承。我们先看一下运用组合如何实现。
attack这一功能不再是一个方法,而是被抽象为一个接口。
interface Attack { public void move(); public void attack(); }
通过对Attack接口的实现,就可以在实现类当中定义不同类型的attack。
class AttackImpl implements Attack { private String move; private String attack; public AttackImpl(String move, String attack) { this.move = move; this.attack = attack; } @Override public void move() { System.out.println(move); } @Override public void attack() { move(); System.out.println(attack); } }
因为attack功能已经被抽象为一个接口,所以Insect类不再需要有attack方法。
class Insect { private int size; private String color; public Insect(int size, String color) { this.size = size; this.color = color; } public int getSize() { return size; } public void setSize(int size) { this.size = size; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } }
Bee类一种Insect类,它具有attack的功能,所以它实现了attack接口:
// 这个封装类封装了一个Attack类型的对象 class Bee extends Insect implements Attack { private Attack attack; public Bee(int size, String color, Attack attack) { super(size, color); this.attack = attack; } public void move() { attack.move(); } public void attack() { attack.attack(); } }
测试类代码,将AttackImpl的实例作为Attack类型的参数传给Bee类的构造函数:
public class InheritanceVSComposition2 { public static void main(String[] args) { Bee a = new Bee(1, "black", new AttackImpl("fly", "move")); a.attack(); // if you need another implementation of move() // there is no need to change Insect, we can quickly use new method to attack Bee b = new Bee(1, "black", new AttackImpl("fly", "sting")); b.attack(); } }
fly
move
fly
sting
3、什么时候该用继承,什么时候该用组合?
以下两条原则说明了应该如何选择继承与组合:
- 如果存在一种IS-A的关系(比如Bee“是一个”Insect),并且一个类需要向另一个类暴露所有的方法接口,那么更应该用继承的机制。
- 如果存在一种HAS-A的关系(比如Bee“有一个”attack功能),那么更应该运用组合。
总结来说,继承和组合都有他们的用处。只有充分理解各对象和功能之间的关系,才能充分发挥这两种机制各自的优点。
本文参考:
- Bloch, Joshua. Effective Java. Pearson Education India, 2008.
- http://stackoverflow.com/questions/49002/prefer-composition-over-inheritance
- http://www.javaworld.com/article/2076814/core-java/inheritance-versus-composition–which-one-should-you-choose-.html
扫盲-继承与组合
...,而继承则是隐式的。组合和继承存在着对应关系:组合中的整体类和继承中的子类对应,组合中的局部类和继承中的父类对应。二者的区别在哪里呢?首先分析一 查看详情
组合与继承及其在 Javascript 中的成本
】组合与继承及其在Javascript中的成本【英文标题】:CompositionvsInheritanceandtheircostsinJavascript【发布时间】:2021-03-2723:59:24【问题描述】:我刚刚发现了javascript中的组合,我一直认为继承是编写javascript的标准方式。我没有得到的... 查看详情
java继承与组合(代码片段)
...;类经过实例化之后的产物对象,则可以用来表示现实中的实体,但是现实世界错综复杂,事物之间可能会存在一些关联,那在设计程序是就需要考虑。我们用下面的代码描述狗和猫.从上述代码可以看出,狗类和猫... 查看详情
组合与继承
组合与继承Java继承组合1、组合 组合:只需在新的类中产生现有类的对象。由于新的类由现有类的对象组成,所以称为组合。(只是复用了现有程序代码的功能,而非它的形式)如下: classA{ privateinta=10; privateintb; priva... 查看详情
继承与组合(代码片段)
...的成员变量和方法。3)当子类中定义的成员变量与父类中的成员变量重名时,子类中的成员变量会覆盖父类中的成员变量,而不是继承父类中的成员变量。4)当子类中的方法签名与父类中的方法签名相同(重名)时(前提是个... 查看详情
重新精读《java编程思想》系列之组合与继承
Java复用代码的两种方式组合与继承。组合组合只需将对象引用置于新类中即可。比如我们有一个B类,它具有一个say方法,我们在A类中使用B类的方法,就是组合。publicclassB{publicvoidsay(){}}publicclassA{publicvoidcombo(){Bb=newB();b.say();}}在j... 查看详情
继承与组合
本文主要说明Java中继承与组合的概念,以及它们之间的联系与区别。首先文章会给出一小段代码示例,用于展示到底什么是继承。然后演示如何通过“组合”来改进这种继承的设计机制。最后总结这两者的应用场景,即到底应... 查看详情
深入理解java中的组合和继承
深入理解Java中的组合和继承Java是一个面向对象的语言。每一个学习过Java的人都知道,封装、继承、多态是面向对象的三个特征。每个人在刚刚学习继承的时候都会或多或少的有这样一个印象:继承可以帮助我实现类的复用。所... 查看详情
为啥继承是强耦合的,而Java中的组合是松耦合的? [复制]
】为啥继承是强耦合的,而Java中的组合是松耦合的?[复制]【英文标题】:whyinheritanceisstronglycoupledwhereascompositionislooselycoupledinJava?[duplicate]为什么继承是强耦合的,而Java中的组合是松耦合的?[复制]【发布时间】:2013-10-0910:20:01... 查看详情
c++中的继承(代码片段)
...式 1.2.2继承方式二.基类和派生类对象的赋值转化三.继承中的作用域 四.派生类的默认成员函数五.继承和友元 六.继承与静态成员七.复杂的菱形继承和菱形虚拟继承7.1菱形继承的问题7.2解决办法7.3虚拟继承实现原理八.继承与组... 查看详情
重新认识java—组合聚合与继承的爱恨情仇(代码片段)
有人学了继承,认为他是面向对象特点之一,就在所有能用到继承的地方使用继承,而不考虑究竟该不该使用,无疑,这是错误的。那么,究竟该如何使用继承呢?java中类与类之间的关系大部分的初学... 查看详情
[c++]——继承赋值兼容规则与组合(这一篇就够了!)(代码片段)
...可以实现赋值呢?(赋值兼容规则原理)继承中的特殊函数(友元函数、静态成员函数)组合什么是组合组合的优缺点继承继承是C++中的特有属性,是类与类之间的一种关系。说到继承 查看详情
java中继承的好处是啥,在组合和继承中该如何取舍?
1,继承提高了代码的重复利用性;2,继承是面向对象的,一类事物可以独立存在,解藕了类与类之间的斗联系。3,继承更利于程序的设计。5,在组合中,一定要把同一类事物放在一起,和现实生活中一样,每一类事物都是有... 查看详情
面向对象——8-继承与组合
8-继承与组合1.使用继承的注意点2.利用组合实现复用 查看详情
[c++]——继承赋值兼容规则与组合(这一篇就够了!)(代码片段)
...可以实现赋值呢?(赋值兼容规则原理)继承中的特殊函数(友元函数、静态成员函数)组合什么是组合组合的优缺点继承继承是C++中的特有属性,是类与类之间的一种关系。说到继承,就不得... 查看详情
11.组合与继承
Inheritance(继承)Composition(复合)Delegation(委托)像字符串类,复数类一把不会和其他类发生关联,但有的类可能需要和其它类发生关联,这就是面向对象的思想。Composition(复合),表示has-a 查看详情
组合与继承——抽象类
】组合与继承——抽象类【英文标题】:compositionandinheritance-abstractclass【发布时间】:2012-10-2417:10:35【问题描述】:假设我有一个超类WorkStation,和两个子类StationNormal、StationAdvanced。我有另一个类Robot,它有一个WorkStation指针,... 查看详情
Swagger 继承与组合
】Swagger继承与组合【英文标题】:SwaggerInheritanceandComposition【发布时间】:2015-03-0721:11:24【问题描述】:在我的“简化”API中,所有响应都派生自(继承)基础“响应”类。响应类组成由一个充满元数据的标头和包含用户请求的... 查看详情