【Effective Java】條16:複合優於繼承

本文所說的繼承都是類和類之間的繼承,而非接口和類之間的繼承

繼承是面向對象的三大特性之一。一般情況下,當子類和父類在同一包下且必須繼承的時候可以採用繼承,畢竟同一包下的代碼由同一個人管理;另外當某個類就是被設計成去繼承的時候,也可以考慮繼承。

但是繼承如果使用不當的話,會導致隨後軟件修改很困難。

假設我們需要實現Set中曾經加入過多少次元素。實現如下:

public class InstrumentedHashSet<E> extends HashSet {

  private long count;

  @Override
  public boolean add(Object o) {
    count++;
    return super.add(o);
  }

  @Override
  public boolean addAll(Collection collection) {
    count += collection.size();
    return super.addAll(collection);
  }

  public long getCount() {
    return count;
  }

  public static void main(String[] args) {
    HashSet hashSet = new HashSet(3);
    hashSet.add(1);
    hashSet.add(2);
    hashSet.add(3);

    InstrumentedHashSet<Integer> instrumentedHashSet = new InstrumentedHashSet<>();
    instrumentedHashSet.addAll(hashSet);
    //結果輸出爲6,而非3
    System.out.println(instrumentedHashSet.getCount());
  }
}

但是結果很不幸,不爲3,而是6。因爲在父類中,addAll調用的就是add方法,這樣addAll加了3,然後還會在add方法中加3次,最終得到6

我們可以在InstrumentedHashSet中不重寫addAll方法,這樣得到count=3,但是隨後HashSetaddAll方法的實現究竟依不依賴於add方法,我們不得而知,可能修改了addAll的實現之後我們忘記修改InstrumentedHashSet類。

另外,也可以在InstrumentedHashSet中重新實現addAll方法,而不是override。但這樣實現太麻煩了,相當於需要重新實現父類中的addAll方法,不符合重複一次規則。

爲了避免以上的問題,我們推薦採用複合:將父類作爲新類的成員屬性。

採用複合改寫上面的代碼:

ForwardingSet.java

public class ForwardingSet<E> implements Set<E> {
  private Set<E> s;

  public ForwardingSet(Set<E> s) {
    this.s = s;
  }

  @Override
  public int size() {
    return s.size();
  }

  @Override
  public boolean isEmpty() {
    return s.isEmpty();
  }

  @Override
  public boolean contains(Object o) {
    return s.contains(o);
  }

  @Override
  public Iterator iterator() {
    return s.iterator();
  }

  @Override
  public Object[] toArray() {
    return s.toArray();
  }

  @Override
  public boolean add(E o) {
    return s.add(o);
  }

  @Override
  public boolean remove(Object o) {
    return s.remove(o);
  }

  @Override
  public boolean addAll(Collection collection) {
    return s.addAll(collection);
  }

  @Override
  public void clear() {
    s.clear();
  }

  @Override
  public boolean removeAll(Collection collection) {
    return s.removeAll(collection);
  }

  @Override
  public boolean retainAll(Collection collection) {
    return s.retainAll(collection);
  }

  @Override
  public boolean containsAll(Collection collection) {
    return s.containsAll(collection);
  }

  @Override
  public Object[] toArray(Object[] objects) {
    return s.toArray(objects);
  }
}

InstrumentedHashSet.java

public class InstrumentedHashSetV2<E> extends ForwardingSet<E> {

  private int count;

  public InstrumentedHashSetV2(Set<E> s) {
    super(s);
  }

  @Override
  public boolean add(E o) {
    count++;
    return super.add(o);
  }

  @Override
  public boolean addAll(Collection collection) {
    count += collection.size();
    return super.addAll(collection);
  }
}

這裏,ForwardingSet.java俗稱轉發類(Forwarding),是設計用來複用的。真正的包裝類還是InstrumentedHashSet.java

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章