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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...