星期日, 10月 15, 2006

DIP: Dependency Inversion Principle

DIP (Dependency Inversion Principle)相依性反向原則:要依賴於抽象,而不要依賴於具體類別。
這個經驗法則建議程式中所有的關係都應終止於介面或抽象類別。以下幾種情形況都應該遵循DIP
  • 變數的類別宣告
  • 參數的類別宣告
  • 方法的傳回型態宣告
  • 型態的轉換
所以這個原則很難遵守,幾乎每個程式都會多少違反DIP。

若一個物件存在其抽象類別,就應當在任何參照此物件的地方使用抽象類別,例如在Java中應該使用介面List而不要直接使用ArrayList
List books = new ArrayList();

DIP假定所有的具體類別都是會變化的並不完全正確,因為某些具體類別是相當的穩定因此並不需要為此發明一個抽象型態。

我自己的經驗是:就先照想法做,不行時再refactor吧!物件生成是以效能為代價,若所有物件都是指向抽象類別,會和SmallTalk一樣慢到不行。

Update:感謝qing的指正,使用抽象類別宣告的影響幾乎感受不到,為了彈性應該盡可能使用抽象類別設計。


參考資料:
軟體工程(Software Engineering;SE)

3 則留言:

qing 提到...

我是覺得啦
hardcode 任何一個名稱就會相依於這個名稱
當這個名稱代表的意義愈一般(也就是愈抽象
時)受到變動而影響的機會就會愈小, 因為這
個名稱所代表的概念是比較一般的, 範圍比
較廣的, 比較有機會繼續容納變動後的概念
這是為什麼希望使用的名稱(也就意謂了概念
) 是愈抽象的愈好

不過文中提到如果都做 upcasting 速度會
變慢可能就不見得. 對採用dynamic
binding的程式語言來說, 就算沒有做
upcasting, 應該也是花一樣的時間來找出
要invoke的method究竟是那一個

鳥毅 提到...

我覺得還是有差,所以做了個小實驗。
時間差一點點啦,幾乎感覺不出來。
我常用子類別另一個原因是子類別的功能比較多,偶爾會直接用子類別的方法。
未cast花3084毫秒,有cast花3153毫秒
測試程式如下:

mport java.util.*;
public class TestSpeed {
public static void main(String[] args) {
//較前的會慢一點 可能是HotSpot 的JIT影響,有興趣的人可以分開測
noCast();
cast();
}

public static void noCast() {
java.util.Calendar start = java.util.Calendar.getInstance();
long lstart = start.getTimeInMillis();

for (int j = 0; j < 100; j++) {
for (int i = 0; i < 65530; i++) {
ArrayList l = new ArrayList();
l.add(i);
l.clear();
}
}
java.util.Calendar end = java.util.Calendar.getInstance();
long lend = end.getTimeInMillis();

System.out.println("未Cast經過:" + (lend - lstart));
}

public static void cast() {
java.util.Calendar start = java.util.Calendar.getInstance();
long lstart = start.getTimeInMillis();

for (int j = 0; j < 100; j++) {
for (int i = 0; i < 65530; i++) {
List l = new ArrayList();
l.add(i);
l.clear();
}
}
java.util.Calendar end = java.util.Calendar.getInstance();
long lend = end.getTimeInMillis();
System.out.println("有Cast經過:" + (lend - lstart));
}
}

Patrick 提到...

經由 interface 呼叫 method (使用 invokeinterface) 通常會比直接呼叫 (使用 invokevirtual) 慢。

不過以上面需要 cast 的這個例子,假如 Java compiler 夠聰明的話,應該要能夠用 invokevirtual 來呼叫 add() 及 clear(),這樣兩者的 performance 就沒有差別了。