首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Effective Java 第 16 條:組合優於繼承 —— 包裝類模式實戰

     編輯:Java 綜合教學

Effective Java 第 16 條:組合優於繼承 —— 包裝類模式實戰

繼承破壞封裝,組合實現復用。本文通過 ForwardingSet + InstrumentedSet 實例,展示如何用「轉發類 + 包裝類」替代繼承,避免父類內部調用邏輯被子類覆寫導致的不可預期行為。

一、 為什麼繼承危險?

  • 子類覆寫方法可能被父類內部調用(如 addAll() 內部循環調用 add()),導致子類邏輯多次執行或狀態不一致。
  • 父類升級新增方法,子類若未覆寫可能暴露未預期行為;若覆寫則維護成本激增。
  • 繼承是 is-a 關係,僅當確滿足「子類真正是父類的一種」時才適用。

二、 包裝類模式兩步走

步驟 1:寫一個「轉發類」實現目標介面,內部持有被包裝對象,所有方法委托給它

public class ForwardingSet<E> implements Set<E> {
    private final Set<E> s; // 組合:持有被包裝 Set

    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<E> iterator() { return s.iterator(); }
    @Override public Object[] toArray() { return s.toArray(); }
    @Override public <T> T[] toArray(T[] a) { return s.toArray(a); }
    @Override public boolean add(E e) { return s.add(e); }
    @Override public boolean remove(Object o) { return s.remove(o); }
    @Override public boolean containsAll(Collection<?> c) { return s.containsAll(c); }
    @Override public boolean addAll(Collection<? extends E> c) { return s.addAll(c); }
    @Override public boolean retainAll(Collection<?> c) { return s.retainAll(c); }
    @Override public boolean removeAll(Collection<?> c) { return s.removeAll(c); }
    @Override public void clear() { s.clear(); }
}

關鍵點: s 為 private final,外部不可見,父類內部調用鏈被切斷。

步驟 2:繼承轉發類,在需要增強的方法中添加業務邏輯,再調用 super.xxx() 轉發

public class InstrumentedSet<E> extends ForwardingSet<E> {
    private int addCount = 0; // 新增統計功能

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

    @Override public boolean add(E e) {
        addCount++;
        return super.add(e); // 轉發給 ForwardingSet → 內部 Set
    }

    @Override public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return super.addAll(c);
    }

    public int getAddCount() { return addCount; }
}

三、 使用示例

Set<String> s = new InstrumentedSet<>(new HashSet<>());
s.add("A");
s.addAll(Arrays.asList("B", "C"));
System.out.println(((InstrumentedSet<String>) s).getAddCount()); // 輸出 3

四、 核心優勢總結

維度繼承組合 + 包裝類
封裝性破壞(子類依賴父類實現細節)保護(內部對象不可見)
耦合度強(父類變更波及子類)弱(只依賴介面契約)
靈活性低(靜態綁定)高(運行時動態替換被包裝對象)
適用場景真正的 is-a 關係幾乎所有「想復用功能」的場景

五、 一句話心法

「除非確定是 is-a,否則用組合:寫個轉發類實現介面,再寫個包裝類繼承轉發類加邏輯。」

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved