Java中的继承与组合

转载 2015年07月07日 22:54:15


本文主要说明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功能),那么更应该运用组合。

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


[Java] 组合与继承的区别

组合和继承都允许在新的类中放置子对象,组合是显式地这样做,而继承是隐式地做。 那两者之间的区别是怎样的呢?又该如何选择呢? 很多人对组合理解地还不是很好,所以我们先来理解一下组合: 组合技术通常用...
  • h_meichuan
  • h_meichuan
  • 2016年04月06日 21:46
  • 1213

java继承和组合的区别,看两个例子,你就明白了

要实现的目标:鸟(Bird)和狼(Wolf)都是动物(Animal),动物都有心跳(beat()),会呼吸(beat()),但是鸟会fly(fly()),狼会奔跑(run()),用java程序实现以上...
  • ycjnx
  • ycjnx
  • 2014年11月09日 20:05
  • 18250

Java编程:组合、继承和代理的区别

组合、继承和代理三者的定义:组合:在新类中new 另外一个类的对象,以添加该对象的特性。 继承:从基类继承得到子类,获得基类的特性。 代理:在代理类中创建某功能的类,调用类的一些方法以获得该类的部...
  • u014762221
  • u014762221
  • 2016年04月09日 21:07
  • 970

深入理解Java中的组合和继承

本文转自:http://www.hollischuang.com/archives/1319 Java是一个面向对象的语言。每一个学习过Java的人都知道,封装、继承、多态是面向对象的三个特征。...
  • dc765940174
  • dc765940174
  • 2016年12月26日 10:15
  • 1995

重新认识java(四) — 组合、聚合与继承的爱恨情仇

有人学了继承,认为他是面向对象特点之一,就在所有能用到继承的地方使用继承,而不考虑究竟该不该使用,无疑,这是错误的。那么,究竟该如何使用继承呢?...
  • qq_31655965
  • qq_31655965
  • 2017年01月21日 11:25
  • 5437

Java 继承和聚合/组合的关系以及 Is A,Has A

继承关系即is a 关系,子类继承父类的属性 方法;比如:我 is a 人;再比如菱形、圆形和方形都是形状的一种,那么他们都应该从形状类继承而不是聚合/组合关系。 聚合/组合关系即has a关系,两...
  • l469121903
  • l469121903
  • 2016年07月01日 14:55
  • 1070

java-组合优于继承

组合和继承对比,及用例分析。
  • wangpeifeng669
  • wangpeifeng669
  • 2014年05月21日 05:59
  • 2799

Java中的复用类——继承、组合和代理hello

一:继承   继承按现有类创建一个对象,不改变现有类,采用现有类的形式向其中添加新代码;(编译器强制你去初始化基类,是is_a 的关系,比如说Student继承Person,则说明Student i...
  • yangquanhui1991
  • yangquanhui1991
  • 2015年11月06日 16:28
  • 1418

继承与组合有什么区别

组合和继承是面向对象中两种代码复用的方式。组合是指在新类里面创建原有类的对象,重复利用已有类的功能。继承是面向对象的主要特性之一,它允许设计人员根据其它类的实现来定义一个类的实现。 组合和继承都...
  • Harrytsz
  • Harrytsz
  • 2017年03月13日 20:03
  • 684

C++浅谈组合和继承

C++有三大特性:封装、继承和多态。 之前提到过继承是为了实现代码的复用,如果子类继承了父类那么就会继承父类所有的数据成员及成员函数,在公有继承的时候保持is-a原则,即每个子类的对象也是父类对象。 ...
  • persistence_s
  • persistence_s
  • 2017年03月08日 22:44
  • 443
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Java中的继承与组合
举报原因:
原因补充:

(最多只允许输入30个字)