《Head First 設計模式》筆記
第一章 策略模式
00設計原則:找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的程式碼放在一起。
把會變化的部分取出並封裝起來,好讓其它部分不會受到影響。結果如何?程式碼變化引起的不經意後果變少,系統變得更有彈性。
00設計原則:針對介面程式設計,而不是針對實現程式設計。
“針對介面程式設計”真正的意思是“針對超型別程式設計”:這裡的介面有多個含義,介面是一個概念,也是一種java的interface構造。”針對介面程式設計“關鍵就在多型。利用多型,程式可以針對超型別程式設計,執行時會根據實際情況執行到真正的行為,不會被綁死在超型別的行為上。這句話可以更明確的說成”變數的宣告型別應該是超型別,通常是一個抽象類或者是一個介面。如此,只要是具體實現此超型別的類所產生的物件,都可以指定給這個變數。這也意味著宣告類時不用理會以後執行的真正物件型別。
OO設計原則:多用組合,少用繼承
使用組合建立系統具有很大的彈性,不僅可以將演算法族封裝成類,更可以“在執行時動態地改變行為”,只要組合的行為物件符合正確的介面標準即可。
策略模式定義了演算法族,分別封裝起來,讓它們之間可以互相替換,此模式讓演算法的變化獨立於使用演算法的客戶。
類圖
JDK
- java.util.Comparator#compare()
- javax.servlet.http.HttpServlet
- javax.servlet.Filter#doFilter()
第二章 觀察者模式
觀察者模式定義了物件之間的一對多依賴,這樣一來,當一個物件改變狀態時,它的所有依賴者都會收到通知並自動更新。
主題和觀察者定義了一對多的關係。觀察者依賴於此主題,只要主題一有變化,觀察者就會被通知。根據通知的風格,觀察者可能因此新值而更新。
當兩個物件之間鬆耦合,它們依然可以互動,但是不太清楚彼此之間的細節。
觀察者提供了一種物件設計,讓主題和觀察者之間鬆耦合。
關於觀察者的一切,主題只知道觀察者實現了某個介面(observer介面)。主題不知道觀察者的具體類是誰,做了些什麼或其他任何細節。
有新型別的觀察者出現時,主題的程式碼不需要修改,假如我們有個新的具體類需要當觀察者,我們不需為了相容新型別而修改主題程式碼,所有要做的就是在新的類裡實現此觀察者介面,然後註冊為觀察者即可主題不在乎別的,它只會傳送通知給所有實現了觀察者介面的物件。
我們可以獨立地複用觀察者和主題,如果我們在其他地方需要使用主題或觀察者,可以輕易地複用,因為二者並非緊耦合。
OO設計原則:為了互動物件之間的鬆耦合設計而努力。
鬆耦合的設計之所以能讓我們建立有彈性的OO系統,能夠應對變化,是因為物件之間的互相依賴降到了最低。
類圖
JDK
- java.util.Observer
- java.util.Observable
- Swing中的GUI框架
- JavaBeans
第三章 裝飾者模式
利用繼承設計子類的行為,是在編譯時靜態決定的,而且所有的子類都會繼承到相同的行為。然而,如果能夠用組合的做法擴充套件物件的行為,就可以在執行時動態地進行擴充套件。可以利用此技巧把多個新職責,甚至是設計超類時還沒想到的職責加在物件上,而且不用修改原來的程式碼。
下圖表示在 DarkRoast 飲料上新增新新增 Mocha 配料,之後又添加了 Whip 配料。DarkRoast 被 Mocha 包裹,Mocha 又被 Whip 包裹。它們都繼承自相同父類,都有 cost() 方法,外層類的 cost() 方法呼叫了內層類的 cost() 方法。
OO設計原則 開發-關閉原則:類應該對擴充套件開放,對修改關閉。
我們的目標是允許類容易擴充套件,在不修改現有程式碼的情況下,就可搭配新的行為,如能實現這樣的目標,有什麼好處?這樣的設計具有彈性,可以應對改變,可以接受新的功能來應對改變的需求。
- 裝飾者和被裝飾者物件有相同的型別
- 可以用一個或多個裝飾者包裝一個物件
- 既然裝飾者和被裝飾者物件有相同的超型別,所以在任何需要原始物件(被包裝的)的場合,可以用裝飾過的物件代替它。
- 裝飾者可以在所委託被裝飾者的行為之前與/或之後,加上自己的行為,已達到特定的目的。
- 物件可以在任何時候被裝飾,所以可以在執行時動態地、不限量地用你喜歡的裝飾者來裝飾物件。
組合和委託可用於在執行時動態地加上新的行為。
裝飾者模式動態地將責任附加到物件上,若要擴充套件功能,裝飾者提供了比繼承更有彈性的替代方案。
類圖
JDK
java.io類 InputStream是抽象元件,FileInputStream、StringBufferInputStream、ByteArrayInputStream都是可以被裝飾者包起來的具體元件;而FilterInputStream是一個抽象裝飾者,BufferedInputStream DataInputStream LineNumberInputStream都是具體裝飾者
第四章 工廠模式
工廠方法模式:定義了一個建立物件的介面,但由子類決定要例項化的類是哪一個。工廠方法讓類把例項化推遲到子類。
所謂的“決定”,並不是指模式允許子類本身在執行時做決定,而是指在編寫建立者類時,不需要知道實際建立的產品是哪一個。選擇了哪個子類,自然就決定了實際建立產品是什麼
OO設計原則(依賴倒置原則):要依賴抽象,不要依賴具體類。
不能讓高層元件(由其他低層元件定義其行為的類)依賴底層元件,而且,不管高層元件或底層元件,兩者都應該依賴於抽象。
類圖
JDK
- java.util.Calendar
- java.util.ResourceBundle
- java.text.NumberFormat
- java.nio.charset.Charset
- java.net.URLStreamHandlerFactory
- java.util.EnumSet
- javax.xml.bind.JAXBContext
抽象工廠模式:抽象工廠模式提供一個介面,用於建立相關或依賴物件的家族,而不需要明確指定具體類。
抽象工廠允許客戶使用抽象的介面來建立一組相關的產品,而不需要知道(或關心)實際產出的具體產品是什麼。這樣一來,客戶就從具體產品中被解耦。
類圖
JDK
- javax.xml.parsers.DocumentBuilderFactory
- javax.xml.transform.TransformFactory
- javax.xml.xpath.XPathFactory
工廠方法模式和抽象工廠模式都是將物件建立的過程封裝起來,以便將客戶程式碼從具體類中解耦。
第五章 單例模式
單件模式:確保一個類只有一個例項,並提供一個全域性訪問點
延遲例項化(lazy instantiaze):如果我們不需要這個例項,它就永遠不會產生,即懶漢式
處理多執行緒:只要把getInstance()變成同步(synchronized)方法,多執行緒災難幾乎可以輕易解決。
改善多執行緒:
1.如果getInstance()的效能對應用程式不是很關鍵,就什麼都別做。
2.使用"急切"建立例項,而不用延遲例項化的做法,即餓漢式。在私有靜態初始化器中建立單件,保證了執行緒安全。
3.用”雙重檢查加鎖“,在getInstance()中減少使用同步:利用雙重檢查加鎖,首先檢查是否例項已經建立了,如果尚未建立,“才”進行同步,這樣一來,只有第一次才會同步。這個做法可以大大減少getInstance()的時間耗費。
violatile關鍵詞確保:當uniqueInstance變數被初始化成Singleton例項時,多個執行緒正確的處理uniqueInstance變數。
類圖
JDK
- java.lang.Runtime#getRuntime()
- java.awt.Desktop#getDesktop()
- java.lang.System#getSecurityManager()
第六章 命令模式
命令模式:將“請求”封裝成物件,以便使用不同的請求,佇列或者日誌來引數化其他物件,命令模式也支援可撤銷的操作。
類圖
JDK
- java.lang.Runnable
- javax.swing.Action
第七章 介面卡模式與外觀模式
介面卡將一個介面轉換成另一個介面,以符合客戶的期望。
介面卡工作起來就如同一箇中間人,它將客戶所發出的請求轉換成廠商類所能理解的請求。
介面卡模式:將一個類的介面,轉換成客戶期望的另一個介面,介面卡讓原本介面不相容的類可以合作無間。
應用:適配迭代器的列舉介面卡EnumeratorIterator
類圖
JDK
- [java.util.Arrays#asList()
- [java.util.Collections#list()
- [java.util.Collections#enumeration()
- [javax.xml.bind.annotation.adapters.XMLAdapter
外觀模式:提供了一個統一的介面,用來訪問子系統中的一群介面。外觀定義了一個高層介面,讓子系統更容易使用。
外觀模式沒有封裝子系統的類,外觀只提供簡化的介面。特徵:提供簡化介面的同時,依然將系統完整的功能暴露出來,以供需要的人使用。
外觀模式不只是簡化了介面,也將客戶從元件的子系統中解耦。
外觀模式和介面卡模式可以包裝一個類或許多類。
外觀與介面卡區別是:外觀的意圖是為了簡化介面,提供一個子系統的簡化介面。而介面卡的意圖是將介面轉換成不同的介面以滿足客戶期望。
OO設計原則 最少知識原則:只和你的密友談話。不要讓太多類耦合在一起,免得修改系統中一部分,會影響到其他部分。如果許多類之間相互依賴,那麼這個系統就會變成一個易碎的系統,它需要花許多成本維護,也會因為太複雜而不容易被其他人瞭解。
如何做到最少知識原則:1.如果某物件是呼叫其它方法的返回結果,不呼叫該物件的方法。
元件:想象成是被例項變數所引用的任何物件,即"has - a"關係。
類圖
第八章 模板方法模式
模板方法定義了一個演算法的步驟,並允許子類為一個或多個步驟提供實現。(即把這些步驟的實現延遲到子類)。
模板方法模式在一個方法中定義一個演算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變演算法結構的情況下,重新定義演算法的某些步驟。
這個模式是用來建立一個演算法的模板。模板就是一個方法。這個方法將演算法定義成一組步驟,其中的任何步驟都可以是抽象的,由子類負責實現。這可以確保演算法的結構保持不變,同時由子類提供部分實現。
為防止子類改變模板方法中的演算法,課件模板方法宣告為final的。
鉤子能夠作為條件控制影響抽象類中的演算法流程。
鉤子目的:1.讓子類實現演算法中可選的部分,或者在鉤子對於子類的實現並不重要的時候,子類可以對鉤子置之不理。2.讓子類能夠有機會對模板方法中某些即將發生的(或剛剛發生的)步驟作出反應。
好萊塢原則:別調用我們,我們會呼叫你。(將決策權放在高層模組中,以便決定如何以及何時呼叫底層模組)
Java API:Arrays.sort()方法 InputStream類的read()方法 JFrame的paint()方法 Applet類的init()、start()、stop()、destroy()、paint()、佇列同步器AbstractQueuedSynchronized等
模板方法模式和策略模式的比較:
策略模式和模板方法模式都封裝演算法,一個用組合,一個用繼承。
類圖
JDK
- java.util.Collections#sort()
- java.io.InputStream#skip()
- java.io.InputStream#read()
- java.util.AbstractList#indexOf()
第九章 迭代器模式與組合模式
迭代器模式提供一種方法順序訪問一個聚合物件中的各個元素,而又不暴露其內部的表示。
迭代器模式能讓我們遊走於聚合內的每個元素,而又不暴露其內部的表示。
把遊走的任務放在迭代器上,而不是聚合上。這樣簡化了聚合的介面和實現,也讓責任各得其所,讓聚合更專注在它所應該專注的事情上面(管理物件集合)。
OO設計原則:一個類應該只有一個引起變化的原因。
類的每個責任都有改變的潛在區域。超過一個責任,意味著超過一個改變的區域。
這個原則告訴我們,儘量讓每個類保持單一責任。
內聚,用來度量一個類或者模組緊密地達到單一的目的或責任。當一個模組或一個類被設計成只支援一組相關的功能時,我們說它具有高內聚;相反,當被設計成支援一組不相關的功能時,我們說它具有低內聚。
類圖
JDK
- java.util.Iterator
- java.util.Enumeration
組合模式允許你將物件組合成樹形結構來表現”整體/部分“層次結構。組合能讓客戶以一致的方式處理個別對象以及物件組合。
組合模式能讓我們能用樹形方式建立物件的結構,樹裡面包含了組合以及個別的物件。
使用組合結構,我們能把相同的操作應用在組合和個別物件上。換句話說,在大多數情況下,我們可以忽略物件組合和個別物件之間的差別。
組合模式提供一個結構,可同時包容個別物件和組合物件。組合模式允許客戶對個別物件以及組合物件一視同仁。組合結構內的任意物件稱為元件,元件可以是組合,也可以是葉子節點。
類圖
JDK
- javax.swing.JComponent#add(Component)
- java.awt.Container#add(Component)
- java.util.Map#putAll(Map)
- java.util.List#addAll(Collection)
- java.util.Set#addAll(Collection)
第十一章 代理模式
代理模式為另一個物件提供一個替身或佔位符以控制對這個物件的訪問。
使用代理模式建立代理物件,讓代表物件控制某物件的訪問,被代理的物件可以是遠端的物件、建立開銷大的物件或需要安全控制的物件。
三種代理控制訪問的方式:
- 遠端代理控制訪問遠端物件
- 虛擬代理控制訪問建立開銷大的資源
- 保護代理基於許可權控制對資源的訪問
代理在結構上類似於裝飾者,但是目的不同。裝飾者模式為物件加上行為,而代理則是控制訪問。
Java內建的代理支援,可以根據需要建立動態代理,並將所有呼叫分配到所選的處理器。
類圖
JDK
- java.lang.reflect.Proxy
- RMI