在〈石頭閒語:類別繼承、介面宣告與模組混成(mix-in)〉,石頭成提到他的學習經驗:
當年在學 Java 時,我便感覺到介面宣告規避了鑽石繼承問題,卻無助於提高程式碼再用性。 Java 的介面宣告,只是規避而非解決鑽石繼承問題。……在動態語言中,雖然多數都採用單一繼承機制,卻又毫不死板,憑藉著動態性提供繼承機制以外的程式碼再用方式。 Ruby 的混成(mix-in)機制就是一個聰明的例子。這個概念也開始被程序員應用於其他語言之中,例如我嘗試於 JavaScript 和 PHP 中實踐混成概念 (PHP 實踐 mix-in 概念之可行性)。
我對所謂的 mix-in 並不熟悉,於是循線看了〈PHP 實踐 mix-in 概念之可行性〉,我發現石頭成所用的實作方式,類似 GOF Design pattern 的 State 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.
請教同人老大:
o.foo() 是從哪來的?整個程式裡並沒有這個方法耶?
我編譯的結果是錯的…
Java 還不是那麼熟悉,還望同人老大指點一二。
另外也發現沒有 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(); 找不到這個方法
}
}
其他部份還是不太瞭解同人老大你的含意所在,還望指點。
感謝網友 jaceju 的指正,原先的 code 有一些問題,而且也沒有封裝 delegate 物件 (Hidden delegate 的委託是外部不可見的,所以才叫 Hidden delegate)。我已修正這些問題,並經測試無誤。請參見本文程式碼與後記,謝謝。
嗯,這樣我可以瞭解了,感謝同人老大的更新。
P.S. 同人老大您也就讀於台科大嗎?我個人是台科大的校友 ^^
Hi jaceju,
是呀,我在台科大 EMBA 進修,希望今年暑假可以變校友。^^
原來你也是台科大校友呀,真巧!