物件導向繼承與程式碼的再用

〈石頭閒語:類別繼承、介面宣告與模組混成(mix-in)〉石頭成提到他的學習經驗:

當年在學 Java 時,我便感覺到介面宣告規避了鑽石繼承問題,卻無助於提高程式碼再用性。 Java 的介面宣告,只是規避而非解決鑽石繼承問題。……在動態語言中,雖然多數都採用單一繼承機制,卻又毫不死板,憑藉著動態性提供繼承機制以外的程式碼再用方式。 Ruby 的混成(mix-in)機制就是一個聰明的例子。這個概念也開始被程序員應用於其他語言之中,例如我嘗試於 JavaScript 和 PHP 中實踐混成概念 (PHP 實踐 mix-in 概念之可行性)。

我對所謂的 mix-in 並不熟悉,於是循線看了〈PHP 實踐 mix-in 概念之可行性〉,我發現石頭成所用的實作方式,類似 GOF Design patternState pattern,利用多型介面與自委託(self-delegate)技巧,可以讓物件同時具有多種型別的行為能力,甚至還可以動態改變物件的型別。

如此看來,石頭成對 Java 介面的負面觀感其實是出自於對 Java 介面使用的誤解。提高程式碼的再用以 Java 技術而言,不是只能靠繼承而已,繼承的誤用只會造成程式碼的僵化與脆弱而已,而實作介面來面對多重繼承的問題,那並沒有比誤用繼承高明到那裡!Java 或其它採用靜態型別的物件導向程式語言,解決一個實體有多種型別的問題,不是靠繼承樹或重覆實作介面,而是利用組合(Composite)角色子型別(Role subtype),即所謂的角色塑模(Dealing with roles),介面的使用是用來除耦-讓設計與實作分開而不是用來當成類別多重繼承的替代方案。

我們其實可以用 Role subtype 概念並用 Hidden delegate 來實作石頭成所展示的同樣範例:

public interface Bar4Intf {
public void bar4(O o);
}


public class O {
private String name;
private Bar4Intf foo;

private O(String name) {
this.name = name;

}

public static O makeBar4O(String name) {
O o = new O(name);

o.foo = new Bar4Impl();

return o;
}

public String getName() {
return name;
}

public void executeBar4() {
foo.bar4(this);
}
}


public class Bar4Impl implements Bar4Intf {
public void bar4(O o) {
if (o != null) {
System.out.println(“bar:” + o.getName());

} else {
System.out.println(“Not in an object context\n”);

}
}
}


public class TestO {
public static void main(String[] args) {
O o = O.makeBar4O(“abc”);
o.executeBar4();
}
}

所以,善用多型的介面,靜態型別程式語言也可以大幅提昇程式碼的再用性。這是物件導向設計的重要原則-開放封閉設計原則(OCP)的實踐,讓物件的型別或代碼可以看成獨立可再用的物件實體來看待,在過程中,介面或抽象類別在其中扮演重要的角色-讓務虛的抽象概念與務實的具體實作能夠充分調合,這是只用繼承觀點來看程式碼的再用所看不見的呀!

後記:

感謝網友 jaceju 的指正,原先的 code 有一些問題,而且也沒有封裝 delegate 物件 (Hidden delegate 的委託是外部不可見的,所以才叫 Hidden delegate)。我已修正這些問題,並經測試無誤。其實 Hidden delegate 重點並不在於 mix-in,而是在於多型的好處,甚至可以動態改變物件本身的型態,除了可以讓代碼及型別的實作可以再用外,它更賦予了軟體設計的彈性。舉這個例子只是想點出,拿動態型別語言的 mix-in 與 Java 的介面相比是不恰當的,因為不管是深度繼承或是用介面實作取代多重繼承,在實務上是不會這樣設計的,介面的使用其實有更有彈性的作法。

P.S. 如果要讓 Bar4Intf 的再用性更高,其實可以把 bar4 方法參數的宣告由類別改成介面,任何類別只要有實作該介面就可以委託 BarIntf 的實例調用 bar4 方法,由此說明應該不難理解介面的解耦效果。

Powered by ScribeFire.

Please follow and like us:
分類: 設計原則, 軟體開發。這篇內容的永久連結

在〈物件導向繼承與程式碼的再用〉中有 5 則留言

  1. jaceju表示:

    請教同人老大:

    o.foo() 是從哪來的?整個程式裡並沒有這個方法耶?

    我編譯的結果是錯的…

    Java 還不是那麼熟悉,還望同人老大指點一二。

  2. jaceju表示:

    另外也發現沒有 getName 方法…

    附上我自己改過可以跑的程式…

    class Foo {

    private String name;
    private IBar bar;

    public Foo(String name) {

    this.name = name;

    }

    public void setName(String name) {

    this.name = name;

    }

    public void setBar(IBar bar) {

    this.bar = bar;

    }

    public String getName() {

    return this.name;

    }

    }

    class Bar implements IBar {

    public void bar(Foo foo) {

    if (foo != null) {

    System.out.println(“bar:” + foo.getName());

    } else {

    System.out.println(“Not in an object context\n”);

    }

    }

    }

    interface IBar {

    public void bar(Foo foo);

    }

    public class FooTest {

    public static void main(String args[]) {
    Foo foo = new Foo(“abc”);

    foo.setBar(new Bar());
    // foo.foo(); 找不到這個方法
    }

    }

    其他部份還是不太瞭解同人老大你的含意所在,還望指點。

  3. jim yeh表示:

    感謝網友 jaceju 的指正,原先的 code 有一些問題,而且也沒有封裝 delegate 物件 (Hidden delegate 的委託是外部不可見的,所以才叫 Hidden delegate)。我已修正這些問題,並經測試無誤。請參見本文程式碼與後記,謝謝。

  4. jaceju表示:

    嗯,這樣我可以瞭解了,感謝同人老大的更新。

    P.S. 同人老大您也就讀於台科大嗎?我個人是台科大的校友 ^^

  5. jim yeh表示:

    Hi jaceju,

    是呀,我在台科大 EMBA 進修,希望今年暑假可以變校友。^^
    原來你也是台科大校友呀,真巧!

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *