網頁

2020/4/4

Effective Java 3e - Item 18: Favor composition over inheritance 筆記

Effective Java 3e - Item 18: Favor composition over inheritance 組合優於繼承 筆記。

重點如標題,使用組合優先於使用繼承。也就是少用繼承,多用組合。或是說使用繼承前先想想是否改用組合來達到同樣目的。

不應繼承不同package的具體類別(concrete class),換句話說不應繼承具體類別(concrete class),除非被繼承類別與繼承類別在同個package內(前提是每個類別要分類在正確的package)。

繼承意味著子類(subclass)可能依賴父類(superclass)的實作及狀態達到某種功能,若父類往後被修改可能導致子類壞掉。

在不清楚具體類別的實作細節的情況下,在子類覆寫方法中引用父類的方法可能會導致非預期的結果,因為父類方法間可能互相引用(self-use),但子類覆寫時忽略了此效果。

即使繼承時覆寫了父類全部的方法,若父類之後新增了一個方法,而子類沒覆寫該方法,客戶端使用該新方法可能造成子類失去原有的效果。

即使子類不覆寫父類的方法僅添加新方法,若父類往後新增一個方法且方法簽章與子類的新方法剛好相同,則導致父類新方法被意外覆寫的問題。

子類無法封裝父類的方法,父類的方法仍可被客戶端存取。

使用組合(compositition)方式解決上述繼承造成的問題,將原本要被繼承類別的實例做為被繼承類別的屬性來使用,改成包裹類(Wrapper)與被包裹類(Wrappered Class)的關係。如此仍可完整封裝並利用原本要繼承的類別,且原類別即使新增方法也不會影響新類別的功能。

例如既有的具體類別B如下。

B

public class B {

    public void doSomething() {
        System.out.println("do something");
    }

}

原本透過A繼承B擴充新的功能。

A

public class A extends B {

    @Override
    public void doSomething() {
        System.out.println("do first");
        super.doSomething();
    }

    public void doAnother() {
        System.out.println("do another");
    }

}

改用composition的方式來達成。

A

public class A {

    private final B b;

    public A (B b) {
        this.b = b;
    }

    public void doSomething() {
        System.out.println("do first");
        b.doSomething();;
    }

    public void doAnother() {
        System.out.println("do another");
    }

}

以上的組合技巧又稱為裝飾者模式(Decorator pattern)

使用繼承前先思考繼承類是否完全屬於被繼承類。例如以A繼承B前先思考是否所有的AB

繼承會破壞封裝效果,只有真正的子類與父類關係才適用繼承。

若類別沒有被設計為要成為被繼承的對象,應避免繼承。


參考:

沒有留言:

張貼留言