面向对象编程(十一)——组合以及与继承的区别

组合(has-a 关系)

我们已经尝试去定义类。定义类,就是新建了一种类型(type)。有了类,我们接着构造相应类型的对象。更进一步,每个类型还应该有一个清晰的接口(interface),供用户使用。

我们可以在一个新类的定义中使用其他对象。这就是组合(composition)。组合是在Java中实现程序复用(reusibility)的基本手段之一。

组合:一个对象是另一个对象的数据成员。

【例子1 code】

package com.gta.testoop.inherit;

/**@Description:  测试组合    
 * @date 2016-2-1 上午10:30:57    
 */
//一个源文件可以定义多个类
//动物Animal类
public class Animal2 {
    String eye;

    public void run(){
        System.out.println("跑跑");
    }

    public void eat(){
        System.out.println("吃吃");
    }
}
//哺乳动物Mammal类
class Mammal2 {
    Animal2 animal2;//作为Mammal2的一个属性
    public void taiSheng(){
        System.out.println("我是胎生");
    }
}
class Bird {
    Animal2 animal2=new Animal2() ;
    
    public void run(){
        animal2.run();
        System.out.println("我是一个小小小鸟,想要飞的更高");
    }
    public void eggSheng(){
        System.out.println("我是卵生");
    }
}

测试类:

package com.gta.testoop.inherit;

/**@Description: 测试组合
 * @date 2016-2-2 上午9:34:11    
 */
public class TestComposition {

    public static void main(String[] args) {
        Bird b=new Bird();
        b.run();
        b.animal2.eat();
    }

}

执行结果:

跑跑
我是一个小小小鸟,想要飞的更高
吃吃
View Code

【例子2 code】充电筒例子;

一个充电电筒中的电池、LED灯、按钮…… 都可以是一个对象。我们可以定义一个Battery类来定义和产生电池对象。而在充电电筒的类定义中,可以用一个电池对象作为其数据成员,来代表电池部分的状态。

我们下面定义一个Battery类,并用power来表示其电量。一个Battery的可以充电(chargeBattery)和使用(useBattery)。我们在随后的Torch类定义中使用Battery类型的对象作为数据成员:

class Battery 
{
    public void chargeBattery(double p)
    {
        if (this.power < 1.) {
            this.power = this.power + p;
        }
    }

    public boolean useBattery(double p)
    {
        if (this.power >= p) {
            this.power = this.power - p;
            return true;
        }
        else {
            this.power = 0.0;
            return false;
        }
    }

    private double power = 0.0;
}

class Torch
{
    /** 
     * 10% power per hour use
     * warning when out of power
     */
    public void turnOn(int hours)
    {
        boolean usable;
        usable = this.theBattery.useBattery( hours*0.1 );
        if (usable != true) {
            System.out.println("No more usable, must charge!");
        }
    }

    /**
     * 20% power per hour charge
     */
    public void charge(int hours)
    {
        this.theBattery.chargeBattery( hours*0.2 );
    }

    /**
     * composition
     */
    private Battery theBattery = new Battery();
}

上面的new为theBattery对象分配内存,不可或缺。

我们定义Battery类。Torch类使用了一个Battery类型的对象(theBattery)来作为数据成员。在Torch的方法中,我们通过操纵theBattery对象的接口,来实现Battery类所提供的功能(functionality)。

我们说,一个Torch对象拥有(has-a)一个Battery对象。上述关系可以表示成:

has-a: 手电有电池 (注意上面的菱形连线)

通过组合,我们可以复用Battery相关的代码。假如我们还有其他使用Battery的类,比如手机,计算器,我们都可以将Battery对象组合进去。这样就不用为每个类单独编写相关功能了。

我们可以增加一个Test类,看看实际效果:

public class Test
{
    public static void main(String[] args)
    {
        Torch aTorch = new Torch();
        System.out.println("Charge: 2 hours");
        aTorch.charge(2);
        System.out.println("First Turn On: 3 hours");
        aTorch.turnOn(3);
        System.out.println("Second Turn On: 3 hours");
        aTorch.turnOn(3);
    }
}

执行结果:

Charge: 2 hours
First Turn On: 3 hours
Second Turn On: 3 hours
No more usable, must charge!
View Code

我们通过组合来使用了电池对象所提供的功能,比如探测电量是否用尽(根据useBattery()的返回值)。

继承(is-a) VS 组合(has-a)

【组合和继承的综合例子】:

要实现的目标:鸟(Bird)和狼(Wolf)都是动物(Animal),动物都有心跳(beat()),会呼吸(beat()),但是鸟会fly(fly()),狼会奔跑(run()),用java程序实现以上描述。

InheritTest.java 使用继承方式实现目标

CompositeTest.java 使用组合方式实现目标

  1 //InheritTest.java 使用继承方式实现目标
  2 class Animal{
  3     private void beat(){
  4         System.out.println("心脏跳动...");
  5     }
  6     public void breath(){
  7         beat();
  8         System.out.println("吸一口气,呼一口气,呼吸中...");
  9     }
 10 }
 11 //继承Animal,直接复用父类的breath()方法
 12 class Bird extends Animal{
 13     //创建子类独有的方法fly()
 14     public void fly(){
 15         System.out.println("我是鸟,我在天空中自由的飞翔...");
 16     }
 17 }
 18 //继承Animal,直接复用父类的breath()方法
 19 class Wolf extends Animal{
 20     //创建子类独有的方法run()
 21     public void run(){
 22         System.out.println("我是狼,我在草原上快速奔跑...");
 23     }
 24 }
 25 public class InheritTest{
 26     public static void main(String[] args){
 27         //创建继承自Animal的Bird对象新实例b
 28         Bird b=new Bird();
 29         //新对象实例b可以breath()
 30         b.breath();
 31         //新对象实例b可以fly()
 32         b.fly();
 33         Wolf w=new Wolf();
 34         w.breath();
 35         w.run();
 36 /*
 37 ---------- 运行Java程序 ----------
 38 心脏跳动...
 39 吸一口气,呼一口气,呼吸中...
 40 我是鸟,我在天空中自由的飞翔...
 41 心脏跳动...
 42 吸一口气,呼一口气,呼吸中...
 43 我是狼,我在草原上快速奔跑...
 44 
 45 输出完毕 (耗时 0 秒) - 正常终止
 46 */
 47     }
 48 }
 49 
 50 //CompositeTest.java  使用组合方式实现目标
 51 class Animal{
 52     private void beat(){
 53         System.out.println("心脏跳动...");
 54     }
 55     public void breath(){
 56         beat();
 57         System.out.println("吸一口气,呼一口气,呼吸中...");
 58     }
 59 }
 60 class Bird{
 61     //定义一个Animal成员变量,以供组合之用
 62     private Animal a;
 63     //使用构造函数初始化成员变量
 64     public Bird(Animal a){
 65         this.a=a;
 66     }
 67     //通过调用成员变量的固有方法(a.breath())使新类具有相同的功能(breath())
 68     public void breath(){
 69         a.breath();
 70     }
 71     //为新类增加新的方法
 72     public void fly(){
 73         System.out.println("我是鸟,我在天空中自由的飞翔...");
 74     }
 75 }
 76 class Wolf{
 77     private Animal a;
 78     public Wolf(Animal a){
 79         this.a=a;
 80     }
 81     public void breath(){
 82         a.breath();
 83     }
 84     public void run(){
 85         System.out.println("我是狼,我在草原上快速奔跑...");        
 86     }
 87 }
 88 public class CompositeTest{
 89     public static void main(String[] args){
 90         //显式创建被组合的对象实例a1
 91         Animal a1=new Animal();
 92         //以a1为基础组合出新对象实例b
 93         Bird b=new Bird(a1);
 94         //新对象实例b可以breath()
 95         b.breath();
 96         //新对象实例b可以fly()
 97         b.fly();
 98         Animal a2=new Animal();
 99         Wolf w=new Wolf(a2);
100         w.breath();
101         w.run();
102 /*
103 ---------- 运行Java程序 ----------
104 心脏跳动...
105 吸一口气,呼一口气,呼吸中...
106 我是鸟,我在天空中自由的飞翔...
107 心脏跳动...
108 吸一口气,呼一口气,呼吸中...
109 我是狼,我在草原上快速奔跑...
110 
111 输出完毕 (耗时 0 秒) - 正常终止
112 */
113     }
114 }

总结:

继承和组合都可以实现代码的复用。

  • "is-a"(是)关系使用继承!
  • "has-a"(拥有)关系使用组合!

最后总结为以下几点:

1)组合(has-a)关系可以显式地获得被包含类(继承中称为父类)的对象,而继承(is-a)则是隐式地获得父类的对象,被包含类和父类对应,而组合外部类和子类对应。

2)组合关系在运行期决定,而继承关系在编译期就已经决定了。

3)组合是在组合类和被包含类之间的一种松耦合关系,而继承则是父类和子类之间的一种紧耦合关系。
4)当选择使用组合关系时,在组合类中包含了外部类的对象,组合类可以调用外部类必须的方法,而使用继承关系时,父类的所有方法和变量都被子类无条件继承,子类不能选择。
5)最重要的一点,使用继承关系时,可以实现类型的回溯,即用父类变量引用子类对象,这样便可以实现多态,而组合没有这个特性。
6)还有一点需要注意,如果你确定复用另外一个类的方法永远不需要改变时,应该使用组合,因为组合只是简单地复用被包含类的接口,而继承除了复用父类的接口外,它甚至还可以覆盖这些接口,修改父类接口的默认实现,这个特性是组合所不具有的。
7)从逻辑上看,组合最主要地体现的是一种整体和部分的思想,例如在电脑类是由内存类,CPU类,硬盘类等等组成的,而继承则体现的是一种可以回溯的父子关系,子类也是父类的一个对象。
8)这两者的区别主要体现在类的抽象阶段,在分析类之间的关系时就应该确定是采用组合还是采用继承。

9)引用网友的一句很经典的话应该更能让大家分清继承和组合的区别:组合可以被说成“我请了个老头在我家里干活” ,继承则是“我父亲在家里帮我干活"。

原文地址:https://www.cnblogs.com/Qian123/p/5176405.html