笔记:Head First设计原则和设计模式
Head First 設計原則和設計模式
- 原則一:封裝變化。找出應用中可能會變化之處,把它獨立出來,不要和那些不需要變化的混在一起
- 原則二:針對接口編程,而不是針對實現編程
- 原則三:多用組合,少用繼承
- 策略模式:定義了算法族,分別封裝起來,讓它們之間可以互相替換,此模式讓算法的變化獨立于使用算法的客戶。
- 觀察者模式:定義了對象之間的一對多依賴,當一個對象改變狀態時,它的所有依賴者都會收到通知并自動更新。
- 原則四:為了交互對象之間的松耦合設計而努力。
- 原則五:類應該對擴展開放,對修改關閉。
- 裝飾者模式:動態地將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。
- 工廠模式
- 簡單工廠模式
- 工廠方法模式:定義了一個創建對象的接口,但由子類決定要實例化的類是哪一個。工廠方法讓類的實例化推遲到子類。
- 依賴倒置原則:要依賴抽象,不要依賴具體類。
- 抽象工廠:提供一個接口,用于創建相關或依賴對象的家族,而不需要明確指定具體類。
- 單件模式:確保一個類只有一個實例,并提供一個全局訪問點。
- 多例模式
- 命令模式:將“請求”封裝成對象,以便使用不同的請求、隊列或者日志來參數化其他對象。命令模式也支持可撤消的操作。
- 適配器模式:將一個類的接口,轉換成客戶期望的另一個接口。適配器讓原本接口不兼容的類可以合作無間。
- 外觀模式:提供一個統一的接口,用來訪問子系統中的一群接口。外觀定義了一個高層接口,讓子系統更容易使用。
- 原則六:最少知識原則,只和你的密友談話。(迪米特法則)
- 模版方法模式:在一個方法中定義一個算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變算法結構的情況下,重新定義算法中的某些步驟。
- 原則七:好萊塢原則,別調用(打電話給)我們,我們會調用(打電話給)你。
- 迭代器模式:提供一種方法順序訪問一個聚合對象中的各個元素,而又不暴露其內部的表示。
- 原則八:單一責任(基本原則):一個類應該只有一個引起變化的原因。
- 組合模式:允許你將對象組合成樹形結構來表現“整體/部分”層次結構。組合能讓客戶以一致的方式處理個別對象以及對象組合。
- 狀態模式:允許對象在內部狀態改變時改變它的行為,對象看起來好像修改了它的類。
- 代理模式:為另一個對象提供一個替身或占位符以控制對這個對象的訪問。
原則一:封裝變化。找出應用中可能會變化之處,把它獨立出來,不要和那些不需要變化的混在一起
封裝變化,使其更具有彈性。eg:把鴨子中容易變化的fly()和quack()分別從duck類中獨立出來
哪些因素導致你必須改變你的程序?
- 我的客戶或用戶決定他們想要其他一些東西或新功能
- 我公司決定使用另一個數據庫,并且從使用不同數據格式的另一個支持商購買數據
- 技術改變,所以我們不得不更新我們的代碼取使用新的協議
- 想要重構系統使其更好
原則二:針對接口編程,而不是針對實現編程
利用接口代表每個行為,行為的每個實現都要實現其中一個接口。這樣,鴨子就不需要知道行為的具體實現了。
“針對接口編程”真正的意思是針對超類型編程,可以利用多態。多態:聲明的變量類型應該時一個超類,通常是一個接口的抽象類,所以分配給這些變量的對象可以是父類的任何具體實現,這意味著類聲明他們不需要知道具體對象類型。
原則三:多用組合,少用繼承
“有一個”可能比“是一個”更好
策略模式:定義了算法族,分別封裝起來,讓它們之間可以互相替換,此模式讓算法的變化獨立于使用算法的客戶。
什么時候使用策略模式?
- 很多相關類僅僅行為不同
- 你需要一個算法的不同變體
- 算法使用的數據用戶不應該知道(避免暴露內部數據結構)
- 類定義了許多行為,這些行為在其操作中顯示為多個條件語句
什么是設計模式:
- 記錄設計面向對象軟件的經驗作為設計模式
- 每個設計模式系統性地命名,解釋以及評估一個重要的且重復出現在面向對象系統中的設計
- 目標是去捕獲設計經驗,用一種人們可以高效使用的形式
- 每個模式描述了一個在我們的環境中反復出現的問題,然后描述了這個問題的解決方案的核心,這樣您就可以將這個解決方案重復使用一百萬次,而不必重復使用相同的方法兩次
設計模式的好處:
- 設計模式為你和其他開發者提供一個共享詞匯表
- 通過讓你在模式層面思考,而不是基本對象層面,提升你關于架構的思考
如何使用設計模式?
- 庫和框架
- DP幫助我們構建的程序更加可維護和可擴展
- DP首先進入你的大腦
設計模式4元素:
- 模式名:用一兩個詞描述一個設計問題,解決方案和結論
- 問題:描述什么時候取應用模式
- 解決方案:描述組成設計的元素,元素之間的關系,責任和合作
- 結論:應用模式的結果和權衡
觀察者模式:定義了對象之間的一對多依賴,當一個對象改變狀態時,它的所有依賴者都會收到通知并自動更新。
上圖中,具體觀察者持有主題的引用,為了在主題那注冊成為觀察者。
使用Java的Observable和Observer接口實現觀察者模式
WeatherData.java
CurrentConditionsDisplay.java
import java.util.Observable; import java.util.Observer;public class CurrentConditionsDisplay implements Observer, DispalyElement{Observable observable;private float temperature;private float humidity;public CurrentConditionsDisplay(Observable observable){this.observable = observable; //構造器持有一個Observable,使用去添加當前對象為Observerobservable.addObserver(this);}public void update(Observable obs, Object arg){if(obs instamceof WeatherData){ // 確保Observable是WeatherData類型WeatherData weatherData = (WeatherData)obs;this.temperature = weatherData.getTemperature();this.humidity = weatherData.getHumidity();display();}}public void dispaly(){System.out.println(temperature + humidity)}}當兩個對象之間松耦合,它們依然可以交互,但不清楚彼此的細節。觀察者提供了一種對象設計,讓主題和觀察者之間松耦合。
任何時候都可以增加新觀察者,因為主題唯一依賴的東西是實現Observer接口的對象列表。
改變主題或觀察者其中一方,并不會影響另一方。
原則四:為了交互對象之間的松耦合設計而努力。
原則五:類應該對擴展開放,對修改關閉。
遵循開放-關閉原則,通常會引入新的抽象層次,增加代碼的復雜度。需要把注意力集中在設計中最有可能改變的地方,然后應用開發-關閉原則。裝飾者模式完全遵循開放-關閉原則。
裝飾者模式:動態地將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。
裝飾者類圖
星巴茲實例類圖
裝飾者繼承被裝飾者而不使用組合?繼承是因為裝飾者和被裝飾者必須是一樣的類型,這里利用繼承達到“類型匹配”,而不是利用繼承獲得“行為”。那么行為從哪來?當將裝飾者與組件組合時,就是加入新行為,得到的新行為不是繼承自超類,而是組合對象來的。
具體裝飾者類
裝飾者模式在JAVA中的應用
寫一個 Lower Case Input Stream 類
工廠模式
簡單工廠模式
簡單工廠把實例化代碼放到工廠里,并不僅是轉移問題到另一個對象(封裝變化),因為簡單工廠類可以有多個客戶。
靜態工廠不需要使用創建對象的方法來實例化對象,但缺點是不能通過繼承來改變創建方法的行為。
簡單工廠模式披薩店實例
工廠方法模式:定義了一個創建對象的接口,但由子類決定要實例化的類是哪一個。工廠方法讓類的實例化推遲到子類。
通過讓子類決定該創建的對象是什么,來達到將對象創建的過程封裝的目的。
在超類中定義工廠方法來處理對象的創建,并將行為封裝在子類中。這樣,超類代碼就和子類對象創建解耦了。
注意,子類決定要實例化的類是哪個,并不是指模式允許子類本身在運行時做決定,而是選擇了使用哪個子類,自然就決定了實際創建的產品是什么。
工廠方法披薩店實例
- 只有一個ConcreteCreator時,工廠方法模式優點: 幫助我們將產品的“實現”從“使用”中解耦,如果增加產品或改變產品的實現,Creator并不會受到影響。
- 各自的ConcreteCreator看起來像利用簡單工廠創建的? 用法不同。雖然每個具體商店實現看起來像簡單工廠,但這里具體商店是擴展自一個類,此類有一個抽象方法createPizza(),由每個具體商店自行負責createPizza()方法的行為。而簡單工廠中,工廠是另一個由Pizzasfore使用的對象。
依賴倒置原則:要依賴抽象,不要依賴具體類。
和“針對接口編程,不針對實現編程”很相似,但這里更強調抽象,這個原則說明:不能讓高層組件依賴低層組件,而且,不管高層或低層組件,都應該依賴于抽象。
- 應用工廠方法后,高層組件(PizzaStore)和低層組件(具體披薩)都依賴了Pizza抽象
- 倒置思考方式:不要從上到下思考披薩店應該實現哪些披薩,而是從下往上思考各種披薩都是披薩,應該共享一個pizza接口。
- 避免違反依賴倒置指導方針:
- 變量不可以持有具體類的引用:使用new就會持有具體類引用,可以改用工廠來避免;
- 不要讓類派生自具體類:派生自具體類就會依賴具體類,應派生自接口。
- 不要覆蓋基類中已實現的方法:如果覆蓋,基類就不是真正適合被繼承的抽象。基類中已實現的方法,應該由所有子類共享。
方針只是指導作用,如果類不容易改變,可直接實例化。
DIP的“倒置”在哪?
- 依賴倒置原則中的“倒置”使因為它轉換你典型地對OO設計的思維方式
- 自上而下的依賴關系圖會反轉自身,高級和低級模塊現在都依賴于抽象。
抽象工廠:提供一個接口,用于創建相關或依賴對象的家族,而不需要明確指定具體類。
抽象工廠模式披薩店實例
注意到:抽象工廠的每個方法實際上看起來都像工廠方法。抽象工廠的方法經常以工廠方法的方式實現,接口內的每個方法負責創建一個具體產品,同時利用抽象工廠的子類來提供具體的做法。
工廠方法和抽象工廠:
1)都負責創建對象
2)工廠方法用繼承,通過子類來創建對象,將客戶從具體類型中解耦。
3)抽象工廠用組合,創建一個產品家族的抽象類型,其子類定義了產品被產生的方法,將客戶從具體產品中解耦。
4)抽象工廠相比工廠方法,優點是可以把一群相關的產品集合起來,缺點是加入新產品就必須改變接口(擴展性較差)。
5)當需要創建產品家族和想讓制造的相關產品集合起來時,使用抽象工廠
6)只是想把客戶代碼從需要實例化的具體類中解耦時,使用工廠方法
單件模式:確保一個類只有一個實例,并提供一個全局訪問點。
多件?
在使用多線程后,單件模式可能出現問題,此時應該把getInstance()變成同步(synchronized)方法。
但每次都進行同步會造成性能損失,其實,只需要在第一次執行此方法時進行同步即可。
雙重檢查加鎖
能不能繼承單件類?
構造器私有,不能用私有構造器擴展類,必須把構造器改成公開的或受保護的,但這樣就不能算單件了。
多例模式
所謂多例(Multiton Pattern)實際上就是單例模式的自然推廣,屬于對象創建類型的模式,多例模式其實就是限制了對象的數量,并且有可能對對象進行重復使用。
特點:
多例類場景:
在java學習過程中,有一個池子的概念一直存在,好比作線程池,數據庫連接池,這個池子是用來對線程,或者數據庫連接對象進行管理的,第一,限制了池子中的對象數量,第二就是能夠在使用過程中達到復用的效果,線程中的線程在執行完畢后,不會被直接回收掉,而會切換成等待狀態,等待下一個任務提交,執行。數據庫連接池也是如此,數據庫操作在連接的時候,如果對數據庫操作完畢后,會把資源釋放,然后等待下一個數據庫操作進行連接。這種設計其實是將對象的應用最大化了,避免了每次連接的時候都需要去創建一個對象。造成對象冗余或者內存升高。
命令模式:將“請求”封裝成對象,以便使用不同的請求、隊列或者日志來參數化其他對象。命令模式也支持可撤消的操作。
一個命令對象通過在特定接收者上綁定一組動作來封裝一個請求。命令對象將動作和接收者包進對象中,只暴露execute()方法,當該方法被調用時,接收者會進行這些動作。
從外面看,其他對象不知道究竟哪個接收者進行哪些動作,只知道調用execute()方法就能達到目的。
命令對象
命令模式類圖
命令對象簡單遙控的Client示例
**復雜遙控器(調用者)**調用者和接受者之間解耦
**NoCommand對象:**空對象,當你不想返回一個有意義的對象時,可以返回一個空對象。客戶也可以將處理null的責任轉移給空對象。
實現undo撤銷功能
宏命令
接收者一定有必要存在嗎?為何命令對象不直接實現execute()方法細節?
盡量設計“傻瓜”命令對象,它只懂得調用一個接收者的一個行為。讓調用者和接收者之間解耦。
可以通過創建PartyCommand,在它的execute()方法調用其他命令,來實現Party模式嗎?
這樣相當于把Party模式“硬編程”到PartyCommand中。利用宏命令可以動態決定PartyCommand是由哪些命令組成,更靈活。
命令模式應用:隊列請求(工作隊列類和進行計算的對象之間解耦)、日志請求
Java語言使用命令模式實現AWT/Swing GUI的 委派事件模型 (Delegation Event Model, DEM)
在AWT/Swing中,Frame、Button等界面組件是請求發 送者,而AWT提供的事件監聽器接口和事件適配器類是抽象命令接口,用戶可以自己寫抽象命令接口的子類來 實現事件處理,即實現具體命令類,而在具體命令類中 可以調用業務處理方法來實現該事件的處理。對于界面 組件而言,只需要了解命令接口即可,無須關心接口的實現,組件類并不關心實際操作,而操作由用戶來實現。
適配器模式:將一個類的接口,轉換成客戶期望的另一個接口。適配器讓原本接口不兼容的類可以合作無間。
適配器模式火雞冒充鴨子示例
萬一系統中新舊并存,舊的部分期望舊接口,但我們已經使用新接口編寫了這一部分,這是該怎么辦?
可以創建一個雙向適配器,支持兩邊的接口。想創建一個雙向適配器,就必須實現所涉及的兩個接口,這樣可以當舊接口,或當新接口使用。
對象適配器和類適配器使用兩種不同的適配方法(分別是組合與繼承)。兩個實現的差異如何影響適配器彈性?
對象適配實現接口,如果方法參數數量不同等,可能一些方法無法適配。
而類適配器使用繼承,如果方法參數數量不同,可以通過改寫父類方法來適配。
適配器模式的應用:將枚舉器適配到迭代器
對于remove()方法,由于枚舉不支持刪除,因為枚舉是一個“只讀”接口,因此適配器無法實現一個有實際功能的remove()方法,最多只能拋出一個運行時異常UnsupportedOprationException。
外觀模式:提供一個統一的接口,用來訪問子系統中的一群接口。外觀定義了一個高層接口,讓子系統更容易使用。
外觀包裝子系統的類,那么需要低層功能的客戶如何接觸這些類?
外觀沒有“封裝”子系統的類,只是提供簡化的接口。外觀一個很好特征:提供簡化接口的同時,依然將系統完整的功能暴露出來,以供需要的人使用。
除了提供簡化接口,外觀模式還允許將客戶實現從任何子系統中解耦。
**適配器模式和外觀模式區別:**外觀和適配器都可以包裝許多類。但適配器的意圖是“改變”接口符合客戶的期望,外觀模式的意圖是提供子系統的一個簡化接口。
外觀模式的家庭影院實例
原則六:最少知識原則,只和你的密友談話。(迪米特法則)
對任何對象,在該對象的方法內,只應該調用以下范圍方法:
- 該對象本身
- 被當做方法的參數而傳遞進來的對象
- 此方法所創建或實例化的任何對象
- 對象的任何組件
**最少知識原則缺點:**導致更多“包裝”類被制造出來,以處理和其他組件的溝通,導致復雜度和開發時間增加,并降低運行時性能。
模版方法模式:在一個方法中定義一個算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變算法結構的情況下,重新定義算法中的某些步驟。
hook鉤子
鉤子被聲明在抽象類中,但只有空的或默認的實現。可以讓子類有能力對算法的不同點進行掛鉤。子類可以自行決定要不要覆蓋鉤子方法,如果不覆蓋,抽象類會提供一個默認的實現。
創建模版方法時,什么時候使用抽象方法,什么時候使用鉤子?
- 當子類必須提供算法中某個方法或步驟的實現時,就用抽象方法;
- 如果算法的這個部分時可選的,就用鉤子。
原則七:好萊塢原則,別調用(打電話給)我們,我們會調用(打電話給)你。
允許低層組件將自己掛鉤到系統上,但高層組件會決定什么時候和這樣使用這些低層組件。
好萊塢原則和模板方法
好萊塢原則和依賴倒置原則的關系:
- 依賴倒置原則教我們盡量避免使用具體類,而多使用抽象,更注重在設計中避免依賴。
- 好萊塢原則是在創建框架或組件上的一種技巧,好讓低層組件能被掛鉤進計算中,而不會讓高層組件依賴低層組件。
工廠方法是模板方法的特例
迭代器模式:提供一種方法順序訪問一個聚合對象中的各個元素,而又不暴露其內部的表示。
迭代器模式的餐廳菜單示例
這個迭代器讓女招待員從具體類的實現中解耦,她不需要知道菜單是使用數組還是ArrayList,只關心她能夠取得迭代器。
原則八:單一責任(基本原則):一個類應該只有一個引起變化的原因。
組合模式:允許你將對象組合成樹形結構來表現“整體/部分”層次結構。組合能讓客戶以一致的方式處理個別對象以及對象組合。
組合模式類圖
組合菜單
狀態模式:允許對象在內部狀態改變時改變它的行為,對象看起來好像修改了它的類。
狀態模式的類圖幾乎跟策略模式的類圖一樣,但兩個模式的差別在于它們的意圖。
狀態模式將一群行為封裝在狀態對象中,context的行為隨時可委托到那些狀態對象中的一個。
策略模式中客戶通常主動指定context所要組合的策略對象是哪個。
狀態模式糖果機具體狀態類示例
糖果機類(Context)
當狀態轉換是固定的時候,狀態決定適合放在Context中,當轉換是動態時,狀態決定就放在狀態類中。
代理模式:為另一個對象提供一個替身或占位符以控制對這個對象的訪問。
Java RMI: RMI提供客戶輔助對象和服務輔助對象,為客戶輔助對象創建和服務對象相同的方法,好處是客戶不必親自寫任何網絡或I/O代碼。
RMI將客戶輔助對象成為stub(樁) ,服務輔助對象成為skeleton(骨架)
制作遠程服務的五個步驟:
客戶如何取stub對象
代理模式糖果機示例
虛擬代理
顯示CD封面示例
創建一個Icon接口從網絡上加載圖像,在加載未完成時顯示“CD封面加載中,請稍后…”,一旦加載完成,代理就把顯示的職責委托給Icon。
總結
以上是生活随笔為你收集整理的笔记:Head First设计原则和设计模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: DNS服务器搭建
- 下一篇: 昨日皇者——Symbian(塞班)