1. 程式人生 > >java小心機(4)| 繼承與組合的愛恨情仇

java小心機(4)| 繼承與組合的愛恨情仇

在java中,有兩種主要複用程式碼的方法:繼承和組合。

繼承,是OOP的一大特性,想必大家都非常熟悉了;組合,其實也很常見,只是不知道它的名字罷了。

繼承

子類擁有父類的基本特性,需使用extend關鍵字實現,宣告某子類繼承於某父類

如下例子,麻雀繼承鳥類

//鳥類
public class Bird {
    public void eat(){
        System.out.println("Bird eat");
    }
    public void fly(){
        System.out.println("Bird fly");
    }
}
//麻雀
public class Sparrow extends Bird{
    public static void main(String[] args){
        Sparrow sparrow = new Sparrow();
        sparrow.eat();
        sparrow.fly();
    }
}

優缺點

優點
  • 子類能自動繼承父類的介面

  • 建立子類的物件時,無須建立父類的物件

缺點
  • 破壞封裝,子類與父類之間緊密耦合,子類依賴於父類的實現,子類缺乏獨立性

  • 支援擴充套件,但是往往以增加系統結構的複雜度為代價

  • 不支援動態繼承。在執行時,子類無法選擇不同的父類

  • 緊耦合

缺點分析

1.為什麼說破壞封裝性? 封裝是指將物件的狀態資訊隱藏在物件內部,不允許外部程式直接訪問物件內部的資訊,而是通過該類所提供的方法來實現對內部資訊的操作和訪問。

如下例子中父類Fruit中有成員變數weight。Apple繼承了Fruit之後,Apple可直接操作Fruit類的成員變數,因此破壞了封裝性!

public class Fruit {
    //成員變數
    public double weight;
    public void info(){
        System.out.println("我是一個水果!重" + weight + "g!");
    }
}

public class Apple extends Fruit {
    public static void main(String[] args){
        Apple a = new Apple();
        a.weight = 10;
        a.info();
    }

2.子類對父類的擴充套件往往會增加系統結構複雜度,繼承樹深度加深,結構越複雜

3.繼承不能支援動態繼承 因為繼承是編譯期就決定下來的,無法在執行時改變

4.為什麼說緊耦合? 意思是父類和子類的耦合性很高,比如說將父類的一個成員變數名稱修改了,子類用到這個變數的地方就需要做修改。 做為一個設計者,應當努力減小耦合關係。

組合

組合通常用於想在新類中使用現有類的功能,而非它的介面。 可能對於名字很陌生,但是用法很熟悉,看下面例子

//鳥類
public class Bird {
    public void eat(){
        System.out.println("Bird eat");
    }
    public void fly(){
        System.out.println("Bird fly");
    }
}
//麻雀
public class Sparrow {
        private Bird bird = new Bird ();

        public void eat(){
            bird.eat();
        }

        public void fly(){
            bird.fly();
        }

        public void walk(){
            System.out.print("Sparrow walk");
        }

        public static void main(String[] args){
            Sparrow sparrow = new Sparrow();
            sparrow.eat();
            sparrow.fly();
            sparrow.walk();
        }
}

優缺點

優點
  • 不破壞封裝,整體類與區域性類之間鬆耦合,彼此相對獨立

  • 具有較好的可擴充套件性

  • 支援動態組合。在執行時,整體物件可以選擇不同型別的區域性物件

  • 整體類可以對區域性類進行包裝,封裝區域性類的介面,提供新的介面

缺點
  • 整體類不能自動獲得和區域性類同樣的介面

  • 建立整體類的物件時,需要建立所有區域性類的物件

繼承與組合對比

相對於組合,繼承的優點:

1、在繼承中,子類自動繼承父類的非私有成員,在需要時,可選擇直接使用或重寫。

2、在繼承中,建立子類物件時,無需建立父類物件,因為系統會自動完成;而在組合中,建立組合類的物件時,通常需要建立其所使用的所有類的物件。

相對於整合,組合的優點:

1、在組合中,組合類與呼叫類之間低耦合;而在繼承中子類與父類高耦合。

2、可動態組合。

如何選擇

從前面的介紹已經優缺點對比中也可以看出,組合確實比繼承更加靈活,也更有助於程式碼維護。 所以,建議在同樣可行的情況下,優先使用組合而不是繼承。因為組合更安全,更簡單,更靈活,更高效。

面向物件中有一個比較重要的原則『多用組合、少用繼承』或者說『組合優於繼承』,這也是六大設計原則之一的合成複用原則。

那我們該如何判斷是否應該使用繼承呢?在java程式設計思想中提供了一個簡單的判斷方法,問一下自己“真的需要向上轉型嗎?”。 如果是必須的,則繼承是必要的。反之則應該好好考慮是否需要繼承。

擴充套件:向上轉型

即用父類引用指向子類物件

什麼時候用到向上轉型?

方法呼叫需要同時接受兩個子類的型別,這時就需要將他們的父類作為方法引數,使用向上轉型將子類轉換為父類型別

以上文中繼承的例子Fruit和Apple,新增Banner類和一個測試類,如下

   public class Bananer extends Fruit {

   }

   public class Test{
   public static void main(String[] args){
        Fruit a = new Apple();//向上轉型
        Fruit b = new Bananer ();//向上轉型        
        getWight(new Apple());//傳入子類,自動向上轉型
        getWight(new Bananer ());//傳入子類,自動向上轉型
    }

    public void getWight(Fruit f){
         System.out.println(f.wight)
    }
  }

image

imageANDKS

  • End -

一個立志成大腿而每天努力奮鬥的年輕人

伴學習伴成長,成長之路你並不孤單!

掃描二維碼,關注公眾號 java從心