EffectiveJava讀書筆記——複合優先於繼承

来源:http://www.cnblogs.com/babycomeon/archive/2016/06/14/5585462.html
-Advertisement-
Play Games

繼承時實現代碼重用的重要手段,但它並非永遠是完成這項工作的最佳工具,不恰當的使用會導致程式變得很脆弱,當然,在同一個程式員的控制下,使用繼承會變的非常安全。想到了很有名的一句話,你永遠不知道你的用戶是如何使用你寫的程式的,一個程式員繼承另一個程式員寫的類也是同樣的危險。 於方法調用不同的是,繼承打破 ...


      繼承時實現代碼重用的重要手段,但它並非永遠是完成這項工作的最佳工具,不恰當的使用會導致程式變得很脆弱,當然,在同一個程式員的控制下,使用繼承會變的非常安全。想到了很有名的一句話,你永遠不知道你的用戶是如何使用你寫的程式的,一個程式員繼承另一個程式員寫的類也是同樣的危險。

      於方法調用不同的是,繼承打破的封裝性。換句話說,子類依賴於其超類中特定功能的實現細節。超類的實現有可能隨著發行版本的不同而變化,如果真的發生了變化,子類可能會遭到破壞,即使它的代碼完全沒有修改過。因而,子類有必要隨著超類的更新而演變。

      為了說明的更加具體一些,假設有一個程式使用了HashSet,想要查詢它被創建以來添加了多少個元素,編寫一個hashSet變數,它記錄下試圖插入的元素數量,並有一個訪問數量的方法

/**
 * 複合優先與繼承
 * @author weishiyao
 *
 * @param <E>
 */
// Broken - Inappropriate use of inheritance
public class InstrumentedHashSet<E> extends HashSet<E> {
	// The number of attempted element insertions
	private int addCount = 0;
	
	public InstrumentedHashSet() {
	}
	
	public InstrumentedHashSet(int initCap, float loadFactor) {
		super(initCap, loadFactor);
	}
	
	@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;
	}
}

  看起來非常合理,但是不能正常工作,假設創建一個實例用addAll方法添加三個元素,

	public static void main(String[] args) {
		InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
		s.addAll(Arrays.asList("a", "b", "c"));
		System.out.println(s.getAddCount());
	}

  這時候我們期望的返回值應該是3,但是事與願違,結果是6,因為在HashSet內部,addAll方法是機遇add方法來實現,相當於每次插入我們都計算了2次,所以結果是6.

  這個問題來源於Override這個動作,但是如果我們不覆蓋現有的方法,可能認為是安全的,雖然這種做法比較安全一些,但是,也並非沒有風險,如果超類在後續版本中增加了一個新的方法,並且不幸的是,這個方法如果和我們寫的方法重名,要麼是返回類型不同,這樣整個程式將會直接down掉,如果返回類型相同,問題返回上面。

  這時候有一種方案可以避免以上的所有問題。不用擴展現有的類,而是在新的類中增加一個私有對象,它引用現有對象的一個實例,這種設計被稱作為複合,因為現有的類變成了新類的一個組件,新類中的每個實力方法都可以調用被包涵的現有類實例中對應的方法,並返回它的結果,這稱為轉發,新類中的方法被稱為轉發方法。

  重新實現上面那個需求

/**
 * 複合優先與繼承
 * @author weishiyao
 *
 * @param <E>
 */
// Reusable forwarding class
public class ForwardingSet<E> implements Set<E> {

	private final 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<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();
	}

}

  

/**
 * 複合優先與繼承
 * @author weishiyao
 *
 * @param <E>
 */
// Wrapper class - uses composition in place of inheritance
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);
	}
	
	@Override
	public boolean addAll(Collection<? extends E> c) {
		addCount++;
		return super.addAll(c);
	}
	
	public int getAddCount() {
		return addCount;
	}
}

  Set介面的存在使得InstrumentedSet類的設計成為可能,因為Set介面保存了HashSet類的功能特性。除了獲得健壯性外,這種設計也帶來了靈活性。InstrumentedSet類實現了Set介面,並擁有單個構造器,它的參數也是Set類型,從本質上來將,這個類把一個Set轉變成了另一餓Set,同時增加了計數的功能。

  前面提到的基於繼承的方法只適用與單個具體的類,並且對於超類中所支持的每個構造器都要求有一個單獨的構造器,於此不同的是這裡的包裝類可以用來包裝任何Set實現,並且可以結合任何以前存在的構造器一起工作。例如:

Set<Date> s1 = new InstrumentedSet<>(new TreeSet<Date>());
Set<E> s2 = new InstrumentedSet<E>(new HashSet<E>());

  因為每一個InstrumentedSet實例都把另一個Set實例包裝起來了,所有InstrumentedSet類被稱為包裝類。這也正是裝飾器模式,因為InstrumentedSet類對一個集合進行了裝飾,為它增加了計數特性。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文適用於所有鍵盤類職業,比如程式員,比如作家,比如編輯,比如水軍,等等... 程式員買得起鍵盤嗎?買得起。 程式員買得起好的機械鍵盤嗎?也買得起。 不過大部分of他們是不會很快決定買的,包括我。因為打從一開始接觸電腦,不管是家裡還是學校甚至網吧,我們用的鍵盤也就是隨便玩玩的,並不會多在意,而等到自 ...
  • 1. download cas 4.2.2 from https://github.com/apereo/cas/releases 2. eclipse import cas 4.2.2 eclipse install SpringSource Update Site for Gradle Inte ...
  • 前段時間因為寫一個業務,對mysql的插入量非常大,在idea開發環境,使得idea一直爆出outofmemry提示,並導致old 區一直漲。使用Mongodb 作為存儲後,不僅插入速度大大提升,idea會卡死的頑疾也解決了。 Mongodb 能夠非常簡單的整合進項目 下麵是我的整合過程 ...
  • 單例模式 概念相關 單例模式 在程式運行過程,一個類只有一個實例 使用場合 在整個應用程式中,共用一份資源(這份資源只需要創建初始化1次) static static關鍵字會在聲明變數的時候分配記憶體,在程式運行期間只分配一次記憶體。之後再訪問時,實際都是在訪問原先分配的記憶體 如果使用static來修飾 ...
  • 對話框不能單獨存在,依賴於窗體,有顯示標題,有模式 獲取Dialog對象,new出來,構造參數:Frame對象,String的標題,模式 窗體內部的內容,Label對象,Button對象,調用Dialog對象的add()方法,把這兩個添加進去 Dialog也是一個普通的窗體,需要設置尺寸和位置 這個 ...
  • 現如今,智能手機是人手一份。每天我們都通過手機與外界溝通交流,手機作為必不可少的媒介,無形中記錄著我們日常生活中的點點滴滴。這些信息主要包括個人位置信息、通信信息、賬號密碼信息、存儲文件信息等四大類。由於Android是開源的,軟體用戶可自行對軟體進行修改、複製及再分發,直接進行信息交換。有些用戶還... ...
  • 本次講述項目背景: 創建Service類,Service下用到dao類。通過在Spring中配置bean,實現在項目啟動時,自動載入這個類 本次只講述配置bean的註意事項,故只給出簡單實例: 創建Service: 在applicationContext.xml下配置bean: 當配置結束後,系統一 ...
  • 註冊工廠是一種很常用的框架書寫方法,它適合於快速創建相同類型的對象。 舉個慄子 比如一個傢具工廠,有沙發、椅子、茶几等等,正常的編程模式是這樣的: 如果想要擴展,就需要繼續定義class,然後new對象。 但是其實沙發的製作與使用時解耦的,使用者並不需要知道沙發、茶几是怎麼製作出來的,只是想使用它而 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...