繼承打破了封裝性
繼承打破了封裝性
繼承打破了封
轉載時請以超鏈接形式標明文章原始出處和作者信息及本聲明
http://dwhstudying.blogbus.com/logs/1599885.html
今天看OOD啟思錄的時候,看到繼承那章,回想到之前看Effective JAVA的時候,看到那里舉的一個繼承的危險性的例子,不過記得不是很精確,于是就翻出那本書:
第十四條,復合優先于繼承。
那里說,與調用類的方法不同,繼承打破了封裝性[Snyder86]。換句話說,一個子類依賴于其超類中特定功能的實現細節。而超類的實現有可能會隨著發行版本的不同而有所變化,如果真的發生了變化則子類可能會被打破,即使它的代碼完全沒有改變。一次一個子類必須要跟著其超類的更新而發展,除非超類是專門為了擴展而被設計的,并且具有很好的文檔說明。
舉個這樣的例子:
public class InstrumentedHashSet extends HashSet {
private int addCount = 0;
........// constructors.
public boolean add( Object o ) {
addCount++;
return super.add( o );
}
public boolean addAll( Collection c ) {
addCount += c.size();
return super.addAll( c );
}
public int getAddCount() {
return addCount;
}
}
這個類看起來非常合理,但是它并不能正常工作。假如我們創建了一個實例,并使用addAll方法添加了三個元素:
InstrumentedHashSet s = new InstrumentedHashSet();
s.addAll( Arrays.asList( new String[] {"Snap","Crackle","Pop"} ) );
這時候,我們一般會期望getAddCount方法會返回3,但是它實際上返回6。錯就出在子類沒有考慮到HashSet內部的實現細節。HashSet的addAll方法是基于它的add方法來實現的,InstrumentedHashSet中的addAll方法首先給addCount增加3,然后通過super.allAll來調用HashSet的addAll實現。然后又順次調用到被InstrumentedHashSet改寫了的add方法,每個元素調用一次。這三次調用又分別給addCount多加了1,所以,總共增加了6:通過addAll方法增加的每個元素都被計算了兩次。
-
在清楚超類的實現細節的話,可以通過修正addAll方法來使子類正確運行。可是,它的功能正確性需要依賴于超類的實現細節。這是一個“Self-use”的例子。而超類的這些實現細節并不是承諾,不能保證不會隨著版本不同而不發生變化。所以這樣得來的子類將是非常脆弱的。
所以,雖然繼承機制的功能非常強大,但是它存在諸多問題。它違背了封裝原則。
因此,Effective JAVA給出第十五條:要么專門為繼承而繼承,并給出文檔說明,要么禁止繼承。
很多時候可以用復合的方法避免繼承。不再是擴展一個已有的類,而是在新的類中增加一個私有域,它引用了這個已有類的一個實例。新類的每一個實例方法都可以調用被包含的已有類的實例中對應的方法,并返回它的結果。這被稱為轉發(forwarding)。
public class InstrumentedSet implements Set {
private final Set s;
private int addCount = 0;
public InstrumentedSet( Set s ){
this.s = s;
}
........// Forwarding methods
}
通過復合和裝發,每一個InstrumentedSet實例都把另一個set實例包裝起來,所以這樣的類可以被稱作包裝類(Wrapper class)。這也正是Decorator模式,因為InstrumentedSet類對一個集合進行了修飾,為它增加了計數特性。