面向對象編程(十一)——組合以及與繼承的區別


組合(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)引用網友的一句很經典的話應該更能讓大家分清繼承和組合的區別:組合可以被說成“我請了個老頭在我家里干活” ,繼承則是“我父親在家里幫我干活"。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM