jim yeh on 三月 27th, 2007

〈石頭閒語:類別繼承、介面宣告與模組混成(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 來實作石頭成所展示的同樣範例:

download: Bar4Intf.java
  1. public interface Bar4Intf {
  2.   public void bar4(O o);
  3. }
download: O.java
  1. public class O {
  2.   private String name;
  3.   private Bar4Intf foo;
  4.  
  5.   private O(String name) {
  6.     this.name = name;</blockquote>
  7.   }
  8.  
  9.   public static O makeBar4O(String name) {
  10.     O o = new O(name);
  11.  
  12.     o.foo = new Bar4Impl();
  13.  
  14.     return o;
  15.   }
  16.  
  17.   public String getName() {
  18.     return name;
  19.   }
  20.  
  21.   public void executeBar4() {
  22.     foo.bar4(this);
  23.   }
  24. }
download: Bar4Impl.java
  1. public class Bar4Impl implements Bar4Intf {
  2.   public void bar4(O o) {
  3.     if (o != null) {
  4.       System.out.println("bar:" + o.getName());</blockquote>
  5.     } else {
  6.       System.out.println("Not in an object context\n");</blockquote>
  7.     }
  8.   }
  9. }
download: TestO.java
  1. public class TestO {
  2.   public static void main(String[] args) {
  3.     O o = O.makeBar4O("abc");
  4.     o.executeBar4();
  5.   }
  6. }

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

後記:

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

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

Powered by ScribeFire.



     

5 Responses to “物件導向繼承與程式碼的再用”

  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 進修,希望今年暑假可以變校友。^^
    原來你也是台科大校友呀,真巧!

Leave a Reply

You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="">