繼承 (inheritance) 是 reuse 程式碼的一種方式,本篇討論的繼承主要為 extends,不包含實作介面 (implement interface) 的部分。繼承與調用方法不同的是它打破封裝性,super class 實作如果改變,subclass 即使沒有變,依然會受到影響。
下面的程式範例為 InstrumentedHashSet,它會計算有多少元素新增,繼承 HashSet 來達到目的,並覆寫 add 與 addAll 方法,加入 addCount 計算邏輯。
public class InstrumentedHashSet<E> extends HashSet<E> {
private int addCount = 0;
public InstrumentedHashSet() {
}
...
@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
}
上述的程式非常合理,實際上是有問題的,只新增 3 個元素,但 addCount 卻是 6。原因為在 super class 使用 addAll 時,其實是使用 add 來協助完成的,在覆寫 add 後,addCount 就自然地再 +3。HashSet 的 addAll 會轉由 AbstractCollection 中的 addAll 來執行,原始碼如下。public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
InstrumentedHashSet<String> s = new InstrumentedHashSet<String>();
s.addAll(Arrays.asList("Snap", "Crackle", "Pop"));
s.getAddCount() // 6
InstrumentedHashSet 是脆弱的,因為它會受到 super class 影響,如果 super class 某次更新將 add 行為更改,subclass 有機會被破壞。那是否在繼承時不要覆寫方法,只新增方法就不會有問題? 或許,但還是無法保證完全不會有問題,如果 subclass 已新增的方法,在 super class 某次更新後,同樣新增該命名的方法,這時一樣又變成覆寫。要解決問題,建議使用 composition (複合),在原 subclass 新增 private field,private field 就是本來的 super class,範例如下。所有的方法都調用原 super class 對應的方法,InstrumentedHashSet 是 wrapper class,將一個 Set 包裝起來,又稱作裝飾者 (Decorator) 模式,這類型的 class 不適合用在 callback function。
public class InstrumentedHashSet<E> {
private int addCount = 0;
private final Set<E> s;
public InstrumentedHashSet(Set<E> s) {
this.s = s;
}
public boolean add(E e) {
addCount++;
return s.add(e);
}
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return s.addAll(c);
}
public int getAddCount() {
return addCount;
}
}
Java 中有的 class 是違反這項規則的,像是 java.util.Stack 繼承 Vector,但兩者本身沒有關係。那什麼時候適合用繼承呢? 當 subclass 是一種 super class 時就適用 (is-a),如果 super class 不是為繼承而設計的,那麼就不應該繼承它,另外在使用繼承時,API 的缺點將會完整的曝露給 subclass,但如果用複合是有機會重新設計的。轉載請註明原文網址 https://cookieandcoketw.blogspot.com/2020/07/effective-java-18-composition.html
沒有留言:
張貼留言