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

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

文章出处:http://www.lupaworld.com/article-242748-1.html

Java中的继承与组合的更多相关文章

  1. 关于Java中的继承和组合的一个错误使用的例子

    [TOC] 关于Java中的继承和组合的一个错误使用的例子 相信绝大多数人都比较熟悉Java中的「继承」和「组合」这两个东西,本篇文章就主要就这两个话题谈论一下.如果我某些地方写的不对,或者比较幼稚, ...

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

    //Java中的继承和组合之间的联系和区别 //本例是继承 class Animal { private void beat() { System.out.println("心胀跳动...& ...

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

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

  4. [译]Java中的继承 VS 组合

    (文章翻译自Inheritance vs. Composition in Java) 这篇文章阐述了Java中继承和组合的概念.它首先给出了一个继承的例子然后指出怎么通过组合来提高继承的设计.最后总结 ...

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

    阅读英文的能力对于程序员来说,是很重要的.这几年也一直在学习英文,今天心血来潮,就在网上找了一篇简短的博文翻译一下.水平一般,能力有限,还请各位看官多多指点. 译文: 本文将会举例说明Java中继承和 ...

  6. java中的继承与oc中的继承的区别

    为什么要使用继承? 继承的好处: (1)抽取出了重复的代码,使代码更加灵活 (2)建立了类和类之间的联系 继承的缺点: 耦合性太强 OC中的继承 1.OC中不允许子类和父类拥有相同名称的成员变量名:( ...

  7. Java中的继承

    我们在以前的学习中,我们会了C#中的继承,今天我们来了解了解Java中的继承,其实都大同小异啦! 1.语法 修饰符 SubClass extends SuperClass(){ //类定义部分 } e ...

  8. extends:类似于java中的继承特征,extends="struts-default"

    extends:类似于java中的继承特征,extends="struts-default"就是继承struts-default.xml,它里面定义了许多跳转类型.拦截器等一些常用 ...

  9. 关于java中的继承

    我们都知道Java中的继承是复用代码.扩展子类的一种方式,继承使得Java中重复的代码能够被提取出来供子类共用,对于Java程序的性能以及修改和扩展有很大的意义,所以这是一个非常重要的知识点. 那么对 ...

随机推荐

  1. [tools] sublime 使用记录

    1. 目录下的文本搜索功能(自带) 1). 把文件夹拖到 sublime 上 2). 在 sublime 上展开要搜索的目录,右击,选择[find in folder] 2. sublime cons ...

  2. 动态规划——概率dp

    所谓概率dp,用动态规划的思想找到一个事件中可能发生的所有情况,然后找到符合要求的那些情况数,除以总数便可以得到符合要求的事件发生的概率.其核心思想还是通过dp来得到事件发生的所有情况,很类似在背包专 ...

  3. POJ 1417 True Liars

    题意:有两种人,一种人只会说真话,另一种人只会说假话.只会说真话的人有p1个,另一种人有p2个.给出m个指令,每个指令为a b yes/no,意思是,如果为yes,a说b是只说真话的人,如果为no,a ...

  4. VS2008 error C2470

    error C2470: '***类' : looks like a function definition, but there is no parameter list; skipping app ...

  5. 【repost】如何学好编程 (精挑细选编程教程,帮助现在在校学生学好编程,让你门找到编程的方向)四个方法总有一个学好编程的方法适合你

    方法(一)编了这么久的程序,一直想找机会总结下其中的心得和方法,但回想我这段编程道路,又很难说清楚,如果按照我走过的所有路来说,显然是不可能的!当我看完了云风的<游戏之旅--编程感悟>和梁 ...

  6. Linux网络编程echo多线程服务器

    echo_server服务器多线程版本 #include <unistd.h> #include <stdlib.h> #include <stdio.h> #in ...

  7. MVC 无法将类型“System.Collections.Generic.List<AnonymousType#1>”隐式转换为“System.Collections.Generic.IList<Mvc3Modeltest.Models.Movie>”。存在一个显式转换(是否缺少强制转换?))

    1.问题: 2.解决方案:强制指定类型. 解决之.

  8. POJ3169 Layout(差分约束系统)

    POJ3169 Layout 题意: n头牛编号为1到n,按照编号的顺序排成一列,每两头牛的之间的距离 >= 0.这些牛的距离存在着一些约束关系:1.有ml组(u, v, w)的约束关系,表示牛 ...

  9. [PWA] 0. Introduce to Offline First

    Why offline first? Imagin you are visiting a website, it is fine if wifi connection is good. It migh ...

  10. Android设置虚线、圆角、渐变

    有图又真相,先上图再说. 点击效果: 设置虚线: <?xml version="1.0" encoding="utf-8"?> <shape  ...