继承与组合

本文主要说明Java中继承与组合的概念,以及它们之间的联系与区别。首先文章会给出一小段代码示例,用于展示到底什么是继承。然后演示如何通过“组合”来改进这种继承的设计机制。最后总结这两者的应用场景,即到底应该选择继承还是组合。

1、继承

假设我们有一个名为Insect(昆虫)的类,这个类包含两个方法:1)移动move(); 2)攻击attack()。
代码如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

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类,就像下面的代码一样:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

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();

    }

}


1

2

3

4

5

6

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方法。

类的继承结构图如下,非常简单:

输出:


1

2

3

Fly

Fly

Attack

Fly被打印了两次,也就是说move方法被调用了两次。但按理来讲,move方法只应当被调用一次,因为无论是昆虫还是蜜蜂,一次攻击前只移动一次。

问题出在子类(即Bee类)的attack方法的重载代码中,也就是super.attack()这一句。因为在父类(即Insect类)中,调用 attack方法时会先调用move方法,所以当子类(Bee)调用super.attack()时,相当于也同时调用了被重载的move方法(注意是子 类被重载的move方法,而不是父类的move方法)。

为了解决这个问题,我们可以采取以下办法:

  1. 删除子类的attack方法。这么做会使得子类的attack方法的实现完全依赖于父类对于该方法的实现(因为子类继承了父类的attack方法)。如果 父类的attack方法不受控制而产生了变更。比如说,父类的attack方法中调用了另外的move方法,那么子类的attack方法也会产生相应的变 化,这是一种很糟糕的封装。
  2. 也可以重写子类的attack方法,像下面这样:

1

2

3

4

public void attack() {

    move();

    System.out.println("Attack");

}

这样保证了结果的正确性,因为子类的attack方法不再依赖于父类。但是,子类attack方法的代码与父类产生了重复(重复的attack方法会使得很多事情变得复杂,不仅仅是多打印了一条输出语句)。所以第二种办法也不行,它不符合软件工程中关于重用的思想。

如此看来,继承机制是有缺点的:子类依赖于父类的实现细节,如果父类产生了变更,子类的后果将不堪设想。

2、组合

在上面的例子中,可以用组合的机制来替代继承。我们先看一下运用组合如何实现。

attack这一功能不再是一个方法,而是被抽象为一个接口。


1

2

3

4

interface Attack {

    public void move();

    public void attack();

}

通过对Attack接口的实现,就可以在实现类当中定义不同类型的attack。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

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方法。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

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接口:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

// 这个封装类封装了一个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类的构造函数:


1

2

3

4

5

6

7

8

9

10

11

12

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();

    }

}


1

2

3

4

fly

move

fly

sting

3、什么时候该用继承,什么时候该用组合?

以下两条原则说明了应该如何选择继承与组合:

  • 如果存在一种IS-A的关系(比如Bee“是一个”Insect),并且一个类需要向另一个类暴露所有的方法接口,那么更应该用继承的机制。
  • 如果存在一种HAS-A的关系(比如Bee“有一个”attack功能),那么更应该运用组合。

总结来说,继承和组合都有他们的用处。只有充分理解各对象和功能之间的关系,才能充分发挥这两种机制各自的优点。

参考:

  1. Bloch, Joshua. Effective Java. Pearson Education India, 2008.
  2. http://stackoverflow.com/questions/49002/prefer-composition-over-inheritance
  3. http://www.javaworld.com/article/2076814/core-java/inheritance-versus-composition–which-one-should-you-choose-.html

原文链接: programcreek 翻译: ImportNew.comcoolgod
译文链接: http://www.importnew.com/12907.html
转载请保留原文出处、译者和译文链接。]

时间: 2024-10-05 03:27:15

继承与组合的相关文章

Objective-C基础1:OC中类的继承和组合

1.类的定义和声明 OC中的类声明是以@interface开始@end结束. OC中的类定义以@implementation开始@end结束. OC中的方法声明: - (void) setName : (NSString*) strName;前面的短线-表示这是一个方法,(void)表示返回值, setName表示方法名称,(NSString*) StrName表示参数是NSString*类型,名称是strName,注意:()一定要加 OC中的成员变量定义在类声明的{}中,这一点跟方法声明不一样

[Think In Java]基础拾遗1 - 对象初始化、垃圾回收器、继承、组合、代理、接口、抽象类

目录 第一章 对象导论第二章 一切都是对象第三章 操作符第四章 控制执行流程第五章 初始化与清理第六章 访问权限控制第七章 复用类第九章 接口 第一章 对象导论 1. 对象的数据位于何处? 有两种方式在内存中存放对象: (1)为了追求最大的执行速度,对象的存储空间和生命周期可以在编写程序时确定,这可以通过将对象置于堆栈或者静态存储区域内来实现.这种方式牺牲了灵活性. (2)在被称为堆的内存池中动态地创建对象.在这种方式,知道运行时才知道对象需要多少对象,它们的生命周期如何,以及它们的具体类型.

java的继承和组合

继承和组合是java中非常常用的两种创建新类型的方法,两者都能提高代码的复用率. 继承主要是想让子类继承父类的基本特性:组合技术通常用于想在新类中使用现有类的功能,而非它的接口.两者的分别是"IS A"和"HAS A"的关系 继承: class Shape { public void draw() { System.out.println("draw a shape"); } public void erase() { System.out.pr

OC学习笔记 面向对象 继承与组合

一.基本概念 程序的世界和人类的“对象”世界在思想上是没有设么区别的,富二代继承了父母,自然就拥有了父母拥有的所有资源,子类继承了父类同样就拥有了父类所有的方法和属性(成员变量). 在这里动物是猫类和狗类的父类,黑猫和白猫类是猫类的子类. 继承的好处: (1)抽取出了重复的代码 (2)建立了类和类之间的联系 继承的缺点: 耦合性太强 二.OC中的继承 @interface Animal:NSObject //动物里继承了NSObject,获得NSObject类的方法: @end @interfa

Java中的继承与组合

本文主要说明Java中继承与组合的概念,以及它们之间的联系与区别.首先文章会给出一小段代码示例,用于展示到底什么是继承.然后演示如何通过“组合”来改进这种继承的设计机制.最后总结这两者的应用场景,即到底应该选择继承还是组合. 1.继承 假设我们有一个名为Insect(昆虫)的类,这个类包含两个方法:1)移动move(): 2)攻击attack(). 代码如下: class Insect { private int size; private String color; public Insect

菜鸟译文(一)——Java中的继承和组合

阅读英文的能力对于程序员来说,是很重要的.这几年也一直在学习英文,今天心血来潮,就在网上找了一篇简短的博文翻译一下.水平一般,能力有限,还请各位看官多多指点. 译文: 本文将会举例说明Java中继承和组合的概念.首先举一个继承的例子,然后展示一下如何用组合来改善继承的设计.最后概括一下如何在它们之间做出选择. 1. 继承 假设我们有一个Insect类.这个类包含两个方法:一个是move(),一个是attack(). class Insect { private int size; private

Java中的继承与组合(转载)

本文主要说明Java中继承与组合的概念,以及它们之间的联系与区别.首先文章会给出一小段代码示例,用于展示到底什么是继承.然后演示如何通过“组合”来改进这种继承的设计机制.最后总结这两者的应用场景,即到底应该选择继承还是组合. 1.继承 假设我们有一个名为Insect(昆虫)的类,这个类包含两个方法:1)移动move(): 2)攻击attack().代码如下: class Insect { private int size; private String color; public Insect(

<Java中的继承和组合之间的联系和区别>

1 //Java中的继承和组合之间的联系和区别 2 //本例是继承 3 4 class Animal 5 { 6 private void beat() 7 { 8 System.out.println("心胀跳动..."); 9 } 10 public void breath() 11 { 12 beat(); 13 System.out.println("吸一口气,吐一口气,呼吸中..."); 14 } 15 } 16 //继承Animal,直接复用父类的bre

新秀翻译(一个)——Java在继承和组合

阅读英文的程序猿的能力,这是非常重要的.过去的几年中一直在学习英语,今天心血来潮,在网上找什么鲍文简要翻译. 普通级,能力有限,看官还请大家多多指点. 译文: 本文将会举例说明Java中继承和组合的概念.首先举一个继承的样例.然后展示一下怎样用组合来改善继承的设计.最后概括一下怎样在它们之间做出选择. 1. 继承 假设我们有一个Insect类.这个类包括两个方法:一个是move().一个是attack(). class Insect { private int size; private Str

java 中继承,组合,重载,重写的实现原理 (转)

我们知道,继承,组合,重载,重写是java语言的面向对象实现的基本特征. 那么在java内部,究竟是如何实现这些面对对象的基本特征的呢? 继承和组合是面向对象中代码复用的主要实现方式,他们可以达到类似的效果,就是提高代码的复用. 组合很简单也很直观,就是在一个类中直接引用另一个类,然后调用引用类的一些方法来完成一些功能,适合领域模型中has-a关系的实现:而继承则适合领域模型中is-a关系的实现. 其实在Java内部,是通过隐式的组合来实现继承的. 子类对象中会保存一个实例对象的引用super,