大話設計模式 全書讀書筆記. 這本書針對各種設計模式, 屬於基礎書籍, 場景和例子比較生動(雖然廢話比較多). 總體來說還是值得看一下. 初學者學習, 工作者溫故知新. ...
第1章 代碼無錯就是優? -- 簡單工廠模式
實常式序: 計算器代碼.
初學者問題:
命名規範, 條件判斷, 異常情況處理.
面向對象編程:
通過封裝, 繼承, 多態把程式的耦合度降低.
容易維護, 擴展, 復用.
舉例: 活字印刷.
改動程式:
- 封裝業務邏輯, 與界面分開.
- 用繼承和多態, 分離各種運算, 使程式可以靈活修改和擴展(增加開根運算).
- 簡單工廠模式: 處理實例化邏輯.
UML類圖
第2章 商場促銷 -- 策略模式
實常式序: 商場收銀軟體.
- 基本需求: 根據商品單價和數量, 計算費用.
- 擴展需求: 增加不同的打折和滿減優惠活動.
簡單工廠實現
相同屬性和功能的對象的抽象集合才是類, 所以不用為每一種折扣寫一個子類.
收費抽象類有三個子類: 正常收費; 打折收費; 返利收費.
缺點: 由於工廠本身包括了所有的收費方式, 商場是可能經常性地更改打折額度和返利額度, 每次維護或擴展收費方式都要改動這個工廠, 以致代碼需要重新編譯部署, 所以它不是最好的方法.
策略模式 Strategy
策略模式(Strategy): 它定義了演算法家族, 分別封裝起來, 讓它們之間可以互相替換, 此模式讓演算法的變化, 不會影響到使用演算法的客戶.
實現: 增加一個Context類, 維護一個對Strategy對象的引用. 這個Context類被客戶端持有.
進一步優化: 策略模式與簡單工廠結合. 將創建具體Strategy的過程移入Context類.
策略模式的優點
- 減少了各種演算法類與使用演算法類之間的耦合.
- 繼承有助於析取一系列演算法中的公共功能.
- 每個演算法有自己的類, 可以單獨做單元測試.
- 演算法修改獨立.
- 消除客戶端條件語句, 避免了大量判斷. (Context中仍然有switch語句, 進一步改進: 反射. 後續章節會講.)
第3章 拍攝UFO -- 單一職責原則
場景: 散步偶遇天空中的不明飛行物, 用手機拍攝後拿回家看不清. 手機不如專業的相機.
單一職責原則
單一職責原則(SRP): 就一個類而言, 應該僅有一個引起它變化的原因.
如果一個類承擔的職責過多, 就等於把這些職責耦合在一起, 一個職責的變化可能會削弱或者抑制這個類完成其他職責的能力. 這種耦合會導致脆弱的設計, 當變化發生時, 設計會遭受到意想不到的破壞.
俄羅斯方塊游戲的設計
考慮在不同平臺上的程式邏輯復用, 將游戲邏輯和界面表示部分分離.
方塊可移動區域: 二維整型數組, 數組的值為是否存在方塊的標誌, 存在為1, 不存在為0.
碰撞和消層變為數值檢測判斷. 下落, 旋轉, 移動等都是在做數組具體項的值的變化.
界面表示邏輯根據數據進行繪製, 根據鍵盤命令調用數組的相應方法進行改變.
第4章 考研求職兩不誤 -- 開放-封閉原則
場景: 考研失敗, 沒有找工作.
舉例: 一國兩制.
開放-封閉原則
開放-封閉原則, 是說軟體實體(類, 模塊, 函數等) 應該可以擴展, 但是不可修改.
對於擴展是開放的, 對於更改是封閉的.
面對需求, 對程式的改動是通過增加代碼進行的, 而不是更改現有的代碼.
應該僅僅對呈現出頻繁變化的那部分做出抽象, 對於應用程式中每個部分都刻意地進行抽象同樣不是一個好主意. 拒絕不成熟的抽象和抽象本身一樣重要.
第5章 會修電腦不會修收音機? -- 依賴倒轉原則
場景: MM電腦藍屏死機, 電話遙控修電腦, 拆掉一根記憶體條.
PC易插拔, 不管哪一個部件出問題, 都可以在不影響別的部件的前提下進行修改或替換.
強內聚, 松耦合.
依賴倒轉原則
- 高層模塊不應該依賴底層模塊, 兩個都應該依賴抽象.
- 抽象不應該依賴於細節, 細節應該依賴於抽象.
- 要針對介面編程, 不要針對實現編程.
如果高層模塊和底層模塊都依賴於抽象(介面或抽象類), 只要介面是穩定的, 那麼無論是高層模塊還是低層模塊都可以很容易地被覆用.
里氏代換原則
里氏代換原則(LSP): 子類型必須能夠替換它們的父類型.
由於子類型的可替換性才使得使用父類類型的模塊在無需修改的情況下就可以擴展.
修收音機
收音機就是典型的耦合過度, 各個部件相互依賴, 難以維護.
第6章 穿什麼有這麼重要? -- 裝飾模式
代碼: 搭配服飾的系統.
- 增加裝扮 -> 開放-封閉原則. -> 抽象類, 繼承.
- 把所需的功能按正確的順序串聯起來進行控制. -> 裝飾模式.
裝飾模式 Decorator
裝飾模式(Decorator), 動態地給一個對象添加一些額外的職責, 就增加功能來說, 裝飾模式比生成子類更為靈活.
裝飾模式把每個要裝飾的功能放在單獨的類中, 並讓這個類包裝它所要裝飾的對象, 因此, 當需要執行特殊行為時, 客戶代碼就可以在運行時根據需要有選擇地, 按順序地使用裝飾功能包裝對象了.
第7章 為別人做嫁衣 -- 代理模式
場景例子: 男生A想追求女生C, 但是他們並不認識, 於是A委托一個認識C的男生B, 向C送禮物.
程式設計: 兩個男生實現了同樣的介面, 介面中規定了送禮物的各種方法.
追求者包含要追求的女生對象和具體的送禮物方法, 而代理追求者包含追求者對象, 其方法實現調用追求者對象的對應方法.
代理模式 Proxy
代理模式(Proxy), 為其他對象提供一種代理以控制對這個對象的訪問.
代理模式應用:
- 遠程代理: 為一個對象在不同的地址空間提供局部代表.
- 虛擬代理: 根據需要創建開銷很大的對象. 通過它來存放實例化需要很長時間的真實對象.
- 安全代理: 用來控制真實對象訪問時的許可權.
- 智能指引: 當調用真實對象時, 代理處理另外一些事.
第8章 雷鋒依然在人間 -- 工廠方法模式
背景故事: 有個學雷鋒做好事的同學病了, 請其他同學幫忙去孤寡老人家裡幹活.
計算器程式的兩種實現:
- 簡單工廠模式實現: switch-case.
- 工廠方法模式實現: 每種演算法對應一個工廠.
簡單工廠 vs. 工廠方法
簡單工廠模式的最大優點在於工廠類中包含了必要的邏輯判斷, 根據客戶端的選擇條件動態實例化相關的類, 對於客戶端來說, 去除了與具體產品的依賴.
簡單工廠的缺點: 對擴展和修改都開放, 違背了開-閉原則.
工廠方法模式 Factory Method
工廠方法模式(Factory Method), 定義一個用於創建對象的介面, 讓子類決定實例化哪一個類. 工廠方法使一個類的實例化延遲到其子類.
符合開-閉原則, 卻把選擇邏輯放在了客戶端.
以簡單工廠和工廠方法模式實現學雷鋒的例子. 工廠方法剋服了簡單工廠違反開-閉原則的缺點, 如果要更換對象, 不需要大的改動, 只要更換工廠就可以.
第9章 簡歷複印 -- 原型模式
程式需求: 寫一個簡歷類, 可以設置基本信息和工作經歷. 最終產生工作經歷不同的多份簡歷.
問題: 三份簡歷需要三次實例化, 如何避免多次實例化?
原型模式 Prototype
原型模式(Prototype), 用原型實例指定創建對象的種類, 並且通過拷貝這些原型創建新的對象.
原型模式其實就是從一個對象再創建另外一個可定製的對象, 而且不需知道任何創建的細節.
.Net中提供了ICloneable
介面.
一般在初始化的信息不發生變化的情況下, 克隆是最好的辦法. 既隱藏了對象創建的細節, 又對性能是大大的提高.
淺複製與深複製
淺複製: 被覆制對象的所有變數都含有與原來的對象相同的值, 而所有的對其他對象的引用都仍然指向原來的對象.
淺複製對於值類型沒什麼問題, 對於引用類型, 就只是複製了引用, 對引用的對象還是指向了原來的對象.
深複製把引用對象的變數指向複製過的新的對象, 而不是原有的被引用的對象.
第10章 考題抄錯會做也白搭 -- 模板方法模式
程式例子: 學生抄題然後解答.
程式1: 學生分別抄題然後給出答案.
問題: 如果老師要改題, 就得修改多處, 而且也存在某些學生可能會抄錯的問題.
改進1: 抽出父類, 處理抄題部分, 只有答題部分不同, 由子類完成.
改進2: 答題部分也有部分內容相同, 只有答案不同. -> 在基類中增加虛方法, 填寫答案, 由子類覆寫填答案.
模板方法模式
模板方法模式, 定義一個操作中的演算法的骨架, 而將一些步驟延遲到子類中. 模板方法使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟.
模板方法模式通過把不變行為搬移到超類, 去除子類中的重覆代碼.
第11章 無熟人難辦事? -- 迪米特法則
場景: 小菜第一天上班, 人事處帶他去IT部門找一個小張領電腦裝電腦. 結果小張臨時出去處理其他事情, 於是小菜就一直等, 而IT部的另外兩個員工很閑卻不幫忙, 因為單子上寫的是小張負責. 於是小菜等了一天, 快結束時小張回來才幫他處理.
問題: 如果公司IT部門有主管, 或者有事直接是整個IT部門負責, 就不會出現具體到個人的資源分配問題. -> 面向介面編程.
迪米特法則
迪米特法則, 如果兩個類不必彼此直接通信, 那麼這兩個類就不應當發生直接的相互作用. 如果其中一個類需要調用另一個類的某一個方法的話, 可以通過第三者轉發這個調用.
在類的結構設計上, 每一個類都應當儘量降低成員的訪問許可權.
第12章 牛市股票還會虧錢? -- 外觀模式
舉例: 股民炒股, 投資多個股票, 自己不易掌握, 買基金, 由專業的經理人進行管理. -> 解除耦合, 外觀模式, 又叫門面模式.
外觀模式 Facade
外觀模式(Facade), 為子系統中的一組介面提供一個一致的界面, 此模式定義了一個高層介面, 這個介面使得這一子系統更加容易使用.
何時使用外觀模式:
- 設計初期的邏輯分層.
- 開發階段增加外觀提供簡單介面.
- 維護遺留系統.
第13章 好菜每回味不同 -- 建造者模式
場景: 大排檔的飯, 一份好吃, 另一份忘了放鹽. 中式快餐依賴於廚師.
提出問題: 為什麼麥當勞肯德基的食物口吻穩定? -> 工作流程規範穩定.
程式需求: 畫小人程式, 畫不同的小人, 如何復用程式, 避免關鍵部分缺失? -> 建造者模式, 又叫生成器模式.
建造者模式 Builder
建造者模式(Builder), 將一個複雜對象的構建與它的表示分離, 使得同樣的構建過程可以創建不同的表示.
Builder
為創建一個Product
對象的各個部件指定的抽象介面.ConcreteBuilder
是具體的建造者, 實現Builder
介面, 構造和裝配各個部件.Director
指揮者, 指揮建造過程, 使用ConcreteBuilder
構建產品.
建造者模式是當創建複雜對象的演算法應該獨立於該對象的組成部分以及它們的裝配方式時適用的模式.
第14章 老闆回來, 我不知道 -- 觀察者模式
場景: 公司同事討論股票, 如果老闆回來, 秘書則打電話通知大家.
代碼實現, 解耦1: 抽象的觀察者; 解耦2: 抽象的通知者介面.
觀察者模式
觀察者模式又叫發佈-訂閱(Publish/Subscribe)模式.
觀察者模式定義了一種一對多的依賴關係, 讓多個觀察者對象同時監聽某一個主題對象. 這個主題對象在狀態發生變化時, 會通知所有觀察者對象, 使它們能夠自動更新自己.
關鍵類: Subject
, ConcreteSubject
, Observer
, ConcreteObserver
.
觀察者模式的不足: 有可能所有觀察者對象是已經寫好的控制項, 它們沒有辦法實現同一個觀察者介面; 每個具體的觀察者, 有可能是不同的方法需要被調用.
事件委托實現
多個觀察者沒有共同的基類, 並且有各自不同名字的更新方法.
此時通知者無法遍歷通知 -> 委托(.Net中的delegate).
首先聲明一個委托類型EventHandler
. 在通知者類中聲明委托事件.
註冊觀察者時將觀察者的更新方法掛鉤到通知者的這個委托事件上. 在通知者通知時調用該委托事件.
委托就是一種引用方法的類型. 一旦為委托分配了方法, 委托將與該方法具有完全相同的行為. 委托可以看作是對函數的抽象, 是函數的"類", 委托的實例將代表一個具體的函數.
一個委托可以搭載多個方法, 所有方法被依次喚起.
前提: 委托對象所搭載的所有方法必須具有相同的參數列表和返回值類型.
場景: 有同學的手機丟了, 委托小菜給班級所有同學發個簡訊通知其他人該同學已經換號, 請大家更新號碼.
第15章 就不能不換DB嗎? -- 抽象工廠模式
場景: 公司有兩個項目業務相同, 但是要用兩種資料庫.
例子代碼存在的問題: 直接創建了具體的資料庫操作類.
解決方法: 用工廠方法模式. 工廠方法模式定義一個用於創建對象的介面, 讓子類決定實例化哪一個類.
代碼改進1: 抽象出對資料庫的一個表的訪問介面, 解除與具體資料庫訪問的耦合.
並定義抽象工廠介面, 由具體的工廠實現實例化.
依然存在問題: 依然存在很多指明具體工廠的代碼.
代碼改進2: 增加了對資料庫其他表的訪問介面. 涉及到多個產品系列的問題 -> 抽象工廠模式.
抽象工廠模式 Abstract Factory
抽象工廠模式(Abstract Factory), 提供一個創建一系列相關或相互依賴對象的介面, 而無需指定它們具體的類.
優點:
- 易於交換產品系列. 只需要改變一個具體的工廠.
- 具體的創建實例過程與客戶端分離, 客戶端通過介面操縱實例.
(開放-封閉原則, 倒轉依賴原則).
缺點:
- 增加功能時要改動的類比較多.
- 實例化具體工廠的地方比較多, 替換實現時仍需要更改多處.
用簡單工廠來改進抽象工廠
去掉了工廠類, 增加一個數據訪問類, 其中用簡單工廠模式來決定實例化哪些具體的資料庫訪問類.
缺點: 如果要增加一系列的實現, 需要在簡單工廠類的每個方法switch中增加case.
用反射 + 抽象工廠的數據訪問程式
解決思路1: 依賴註入框架.
解決思路2: 反射.
利用字元串來實例化對象, 而變數是可以更換的.
問題: 更改資料庫還是需要去改程式(db名稱)重新編譯.
改進: 用配置文件來解決更改變數的問題.
第16章 無盡加班何時休 -- 狀態模式
程式: 不同的時間對應不同的工作狀態.
代碼的壞味道: 方法過長, 有很多的判斷分支.
違背了單一職責原則和開放-封閉原則.
狀態模式 State
狀態模式(State), 當一個對象的內在狀態改變時允許改變其行為, 這個對象看起來像是改變了其類.
狀態模式主要解決的是當控制一個對象狀態轉換的條件表達式過於複雜時的情況. 把狀態的判斷邏輯轉移到表示不同狀態的一系列類當中, 可以把複雜的判斷邏輯簡化.
狀態模式的好處是將與特定狀態相關的行為局部化, 並且將不同狀態的行為分割開來.
狀態模式通過把各種狀態轉移邏輯分佈到State
的子類之間, 來減少相互間的依賴.
什麼時候考慮使用狀態模式?
當一個對象的行為取決於它的狀態, 並且它必須在運行時刻根據狀態改變它的行為時, 就可以考慮使用狀態模式了.
第17章 在NBA我需要翻譯 -- 適配器模式
背景: 姚明剛去NBA時需要翻譯.
適配器模式 Adapter
適配器模式(Adapter), 將一個類的介面轉換成客戶希望的另外一個介面. Adapter模式使得原本由於介面不相容而不能一起工作的那些類可以一起工作.
聯想: 電源適配器, 翻譯.
類適配器模式: 通過多重繼承. (某些語言不支持.)
對象適配器模式: 利用包含.
何時使用適配器模式?
使用一個已經存在的類, 但如果它的介面, 也就是它的方法和你的要求不相同時, 就應該考慮用適配器模式.
通常是在開發後期或維護期再考慮使用適配器模式; 在設計初期, 應該儘量為類似的功能設計相同的介面, 或考慮通過重構統一介面.
在雙方都不太容易修改時使用適配器模式: 比如使用第三方組件.
.Net中的DataAdapter
.
扁鵲三兄弟醫術的例子: 事後控制不如事中控制, 事中控制不如事前控制.
適配器模式是後期才考慮的一個模式.
第18章 如果再回到從前 -- 備忘錄模式
背景: 游戲存檔, 下棋悔棋, 文檔中的撤銷, 網頁中的後退.
程式: 游戲角色的數據存檔.
第一版程式的缺點: 游戲角色的細節暴露給了客戶端, 保存和恢復都暴露了實現細節, 不好擴展和修改.
備忘錄模式 Memento
備忘錄(Memento): 在不破壞封裝性的前提下, 捕獲一個對象的內部狀態, 併在該對象之外保存這個狀態. 這樣以後就可將該對象恢復到原先保存的狀態.
Originator
: 發起人, 負責創建一個備忘錄Memento
, 用以記錄當前時刻它的內部狀態, 並可使用備忘錄恢復內部狀態.Originator
可根據需要決定Memento
存儲哪些內部狀態.Memento
: 備忘錄, 負責存儲Originator
對象的內部狀態, 並可防止Originator
以外的其他對象訪問備忘錄.Caretaker
: 管理者, 負責保存好備忘錄(Memento
), 不能對備忘錄的內容進行操作或檢查.
全部狀態保存可以用clone的方式實現, 更多可能的情況是保存部分狀態, 用備忘錄.
適用情形:
- 備忘錄模式適用於功能比較複雜的, 需要維護或記錄屬性歷史的類, 或者需要保存的屬性只是眾多屬性中的一小部分時.
- 使用命令模式時, 如果需要實現命令的撤銷功能, 那麼可以使用備忘錄模式來存儲可撤銷操作的狀態.
- 一些對象的內部信息必須保存在對象以外的地方, 但是必須要由對象自己讀取. 這時使用備忘錄可以把複雜對象內部信息對其他的對象屏蔽起來.
- 當角色狀態改變的時候, 有可能這個狀態無效, 這時候可以使用暫時存起來的備忘錄將狀態複原.
第19章 分公司 = 一部門 -- 組合模式
場景: 為一個有很多分公司的大公司寫OA系統. 每個分公司, 辦事處和總公司一樣都有相應的人力, 財務等部門, 但系統要求總部和分公司不是平行的, 仍保持一定的樹狀結構.
分析: 整體與部分可以一致對待.
例子: 賣電腦的商家可以賣電腦和配件; 複製文件可以逐個文件複製也可以複製文件夾; 文本編輯, 單個文字和整段文字都可以更改樣式.
組合模式 Composite
組合模式(Composite), 將對象組合成樹形結構以表示'部分-整體'的層次結構, 組合模式使得用戶對單個對象和組合對象的使用具有一致性.
Component
: 組合中的對象聲明介面.
Leaf
: 葉子節點.
Composite
: 有枝節點.
透明方式: Component
中聲明所有用來管理子對象的方法. 好處: 葉子節點和枝節點對外沒有區別, 具備完全一致的行為介面. 問題: 葉子節點中的一些方法實現是沒有意義的.
安全方式: Component
中不去聲明用於管理子對象的方法. 好處: 葉子節點不用實現這些方法. 問題: 不夠透明, 樹葉和樹枝將不具有相同的介面, 客戶端的調用需要做相應的判斷.
何時使用組合模式:
- 需求中是提現部分與整體的層次結構.
- 希望用戶可以忽略組合對象與單個對象的區別.
例子: 自定義控制項.
第20章 想走? 可以! 先買票 -- 迭代器模式
場景: 公交汽車售票員對車廂里的所有人和大件行李進行遍歷, 讓大家買票.
迭代器模式 Iterator
迭代器模式(Iterator), 提供一種方法順序訪問一個聚合對象中各個元素, 而又不暴露該對象的內部表示.
當你需要訪問一個聚集對象, 而且不管這些對象是什麼都需要遍歷的時候, 另外, 你需要對聚集對象有多種方式遍歷時, 就應該考慮用迭代器模式.
迭代器模式為遍歷不同的聚集結構提供如開始, 下一個, 是否結束, 當前哪一項等統一的介面.
很多高級編程語言已經吧這個模式做在語言中了.
第21章 有些類也需要計劃生育 -- 單例模式
程式實例: 窗體程式, 希望工具箱窗體只出現一次.
簡單解決方案1: 聲明全局變數, 判斷null.
缺點: 多個使用的地方會出現重覆的判斷代碼. -> 把它們提煉到同一個地方.
改進: 由類自己控制實例化及判斷.
- 構造函數私有.
- 靜態變數.
- 靜態獲取方法, 創建和訪問唯一實例.
單例模式 Singleton
單例模式(Singleton), 保證一個類僅有一個實例, 並提供一個訪問它的全局訪問點.
多線程時的單例: lock加鎖.
性能進一步改良: 雙重鎖定(Double-Check Locking).
靜態初始化: sealed阻止派生; 變數標記為readonly.
第22章 手機軟體何時統一 -- 橋接模式
場景: 不同手機品牌和不同的應用軟體如何設計類.
兩種複雜並且不合理的設計:
- 手機品牌抽象類 <- 手機品牌具體類 <- 手機品牌下的各種應用類.
- 手機軟體抽象類 <- 手機軟體具體類 <- 不同品牌版本的具體應用類.
用繼承的缺點:
- 對象的繼承關係是在編譯時就定義好了, 所以無法在運行時改變從父類繼承的實現.
- 子類的實現與父類有非常緊密的依賴關係, 以至於父類實現中的任何變化必然會導致子類發生變化.
- 當你需要復用子類時, 如果繼承下來的實現不適合解決新的問題, 則父類必須重寫或被其他更適合的類替換.
繼承這種依賴關係限制了靈活性並最終限制了復用性.
(不要拿著錘子看所有東西都成了釘子.)
一定要在是'is-a'的關係時再考慮使用繼承.
合成/聚合復用原則
合成/聚合復用原則(CARP), 儘量使用合成/聚合, 儘量不要使用類繼承.
合成(Composition, 也有翻譯成組合)和聚合(Aggregation)都是關聯的特殊種類.
- 合成則是一種強的'擁有'關係, 體現了嚴格的部分和整體的關係, 部分和整體的生命周期一樣.
- 聚合表示一種弱的'擁有'關係, 體現的是A對象可以包含B對象, 但B對象不是A對象的一部分.
例子: 大雁和翅膀是合成關係; 大雁和雁群是聚合關係.
改進後的松耦合例子程式:
- 手機品牌類 <- 具體手機品牌類.
- 手機軟體類 <- 具體軟體類.
- 手機品牌抽象類包含手機軟體抽象類對象.
改進後增加軟體和手機品牌都只需要增加新的類 -> 開放-封閉原則.
橋接模式 Bridge
橋接模式(Bridge), 將抽象部分與它的實現部分分離, 使它們都可以獨立地變化.
這裡的實現指的是抽象類和它的派生類用來實現自己的對象.
解釋: 實現系統可能有多角度分類, 每一種分類都有可能變化, 那麼就把這種多角度分離出來讓它們獨立變化, 減少它們之間的耦合.
第23章 烤羊肉串引來的思考 -- 命令模式
場景: 烤肉攤: 烤串人和客戶之間緊密耦合, 比較混亂; 烤肉店: 由服務員記錄客戶請求並記錄, 再通知烤肉師傅, 利於管理, 支持撤銷.
松耦合設計: 定義抽象命令類, 其中包含烤肉師傅; 具體命令類中烤肉師傅執行具體行為; 服務員類包含命令對象, 通知方法通知命令執行.
進一步擴展: 在服務員類中: 增加盛放命令的容器; 對不合理命令進行回絕判斷; 記錄日誌和取消訂單; 遍歷命令通知執行.
命令模式 Command
命令模式(Command), 將一個請求封裝為一個對象, 從而使你可用不同的請求對客戶進行參數化; 對請求排隊或記錄請求日誌, 以及支持可撤銷的操作.
命令模式的作用:
- 能較容易地設計一個命令隊列.
- 可以較容易地將命令記入日誌.
- 允許接收請求的一方決定是否要否決請求.
- 可以容易地實現對請求的撤銷和重做.
- 由於新加的具體命令類不影響其他類, 因此新加具體命令類很容易.
- 把請求一個操作的對象與知道怎麼執行一個操作的對象分割開.
第24章 加薪非要老總批? -- 職責鏈模式
背景: 小菜要求加薪, 向經理提出, 經理向總監提出, 總監向總經理提出.
程式: 申請處理程式, 申請(包含類別, 內容, 數量)提交給管理者, 每個管理者根據申請細節決定自己是否能夠處理(比較長的方法, 多條分支).
職責鏈模式 Chain of Responsibility
職責鏈模式(Chain of Responsibility): 使多個對象都有機會處理請求, 從而避免請求的發送者和接收者之間的耦合關係. 將這些對象連成一條鏈, 並沿著這條鏈傳遞該請求, 直到有一個對象處理它為止.
當客戶提交一個請求時, 請求是沿鏈傳遞直至有一個ConcreteHandler
對象負責處理它.
接收者和發送者都沒有對方的明確信息, 且鏈中的對象自己也並不知道鏈的結構. 結果是職責鏈可簡化對象的相互連接, 它們僅需保持一個指向其後繼者的引用, 而不需保持它所有的候選接受者的引用.
第25章 世界需要和平 -- 中介者模式
中介者模式又叫調停者模式.
舉例: 聯合國組織.
'迪米特法則', 如果兩個類不必彼此直接通信, 那麼這兩個類就不應當發生直接的相互作用. 如果其中一個類需要調用另一個類的某一個方法的話, 可以通過第三者轉發這個調用.
通過中介者對象, 可以將系統的網狀結構變成以中介者為中心的星形結構, 每個具體對象不再通過直接的聯繫與另一個對象發生相互作用, 而是通過'中介者'對象與另一個對象發生相互作用.
之前'迪米特'法則的例子: 公司的IT部門管理, 由主管來協調工作.
中介者模式 Mediator
中介者模式(Mediator), 用一個中介對象來封裝一系列的對象交互. 中介者使各對象不需要顯式地相互引用, 從而使其耦合鬆散, 而且可以獨立地改變它們之間的交互.
程式例子1: 不同的同事類之間通過中介者對象來通信.
程式例子2: 國家之間通過聯合國安理會通信.
中介者模式優點:
Mediator
的出現減少了各個Colleague
的耦合, 使得可以獨立改變和復用各個類.- 把對象協作進行了抽象, 將中介作為一個獨立的概念並將其封裝在一個對象中, 這樣關註的對象就從對象各自本身的行為轉移到它們之間的交互上來, 也就是站在一個更巨集觀的角度去看待系統.
中介者模式的缺點:
- 具體中介類可能會變得非常複雜, 不容易維護.
中介者模式例子: Windows程式中的Form或Web網站程式的aspx.
中介者模式一般應用於一組對象以定義良好但是複雜的方式進行通信的場合, 以及想定製一個分佈在多個類中的行為, 而又不想生成太多的子類的場合.
第26章 項目多也別傻做 -- 享元模式
背景: 小菜接了多個小型外包項目, 給私營業主做網站. 多個網站要求類似, 形式有一些變化.
問題: 真的需要每個網站都分別有一份代碼, 每個網站租用一個虛擬空間嗎?
它們本質上是一樣的代碼, 如果網站增多, 實例增多, 對伺服器的資源浪費嚴重.
共用代碼: 大型的博客網站, 電子商務網站, 裡面的每一個博客或商家也可以理解為一個小的網站. -> 利用用戶ID來區分不同的用戶, 具體數據和模板可以不同, 但代碼核心和資料庫卻是共用的. -> 節省伺服器資源; 代碼維護和擴展都更容易.
享元模式 Flyweight
享元模式(Flyweight), 運用共用技術有效地支持大量細粒度的對象.
FlyweightFactory
: 創建並管理Flyweight
對象. 它主要用來確保合理地共用Flyweight
, 當用戶請求一個Flyweight
時,FlyweightFactory
對象提供一個已創建的實例或者創建一個(如果不存在的話).Flyweight
: 所有具體享元類的超類或介面, 通過這個介面,Flyweight
可以接受並作用於外部狀態. 其子類並不強制共用.
網站共用代碼: 同樣類型的網站使用同一個實例, 減少實例個數.
問題: 網站的數據不同? -> 外部狀態.
內部狀態與外部狀態
- 內部狀態: 在享元對象內部並且不會隨環境改變而改變的共用部分.
- 外部狀態: 隨環境改變而改變的, 不可以共用的狀態.
享元模式可以避免大量非常相似類的開銷. 在程式設計中, 有時需要生成大量細粒度的類實例來表示數據. 如果能發現這些實例除了幾個參數外基本上都是相同的, 有時就能夠大幅度地減少需要實例化的類的數量. 如果能把那些參數移到類實例的外面, 在方法調用時將它們傳遞進來, 就可以通過共用大幅度地減少單個實例的數目.
享元模式應用
- 如果一個應用程式使用了大量的對象, 而大量的這些對象造成了很大的存儲開銷.
- 對象的大多數狀態可以是外部狀態, 如果刪除對象的外部狀態, 那麼可以用相對較少的共用對象取代很多組對象, 此時可以考慮使用享元模式.
應用: .Net中的string: 相同的字元串常量其實是共用記憶體的, 所以引用相同.
圍棋, 五子棋, 跳棋等: 顏色是內部狀態, 位置是外部狀態.
第27章 其實你不懂老闆的心 -- 解釋器模式
背景: 從老闆談話中聽出弦外之音.
解釋器模式 Interpreter
解釋器模式(Interpreter), 給定一個語言, 定義它的文法的一種表示, 並定義一個解釋器, 這個解釋器使用該表示來解釋語言中的句子.
解釋器模式需要解決的是, 如果一種特定類型的問題發生的頻率足夠高, 那麼可能就值得將該問題的各個實例表述為一個簡單語言中的句子. 這樣就可以構建一個解釋器, 該解釋器通過解釋這些句子來解決該問題.
比如:
- 匹配字元串 -> 正則表達式.
- 瀏覽器解釋HTML文法, 顯示網頁.
- 機器人指令.
當有一個語言需要解釋執行, 並且你可以將該語言中的句子表示為一個抽象語法樹時, 可使用解釋器模式.
好處: 可以很容易地改變和擴展文法, 因為該模式使用類來表示文法規則, 你可使用繼承來改變或擴展該文法. 也比較容易實現文法, 因為定義抽象語法樹中各個節點的類的實現大體類似, 這些類都易於直接編寫.
不足: 解釋器模式為文法中的每一條規則至少定義了一個類, 因此包含許多規則的文法可能難以維護和管理. 建議當文法非常複雜時, 使用其他的技術如語法分析程式或編譯器生成器來處理.
程式實例: 音樂解釋器.
第28章 男人和女人 -- 訪問者模式
背景: 寫一個程式, 輸出男人和女人在相同狀態下的不同反應, 比如: 男人成功時xxx; 女人成功時yyy.
關鍵點:
- 人分為男人和女人兩類, 這個分類是穩定的. 所以可以在抽象的狀態類中, 聲明男人反應和女人反應兩個方法. 這樣方法個數是穩定的, 不會很容易地發生變化.
- 而人的抽象類中有一個抽象方法接受狀態對象.
- 客戶程式中將具體程式作為參數傳遞給具體的人的類. (第一次分派.)
- 具體的男人或女人的類在接受方法中調用狀態對象中對應自己的那個方法, 並將自己作為參數傳遞進去. (第二次分派.)
訪問者模式 Visitor
訪問者模式(Visitor), 表示一個作用於某對象結構中的各元素的操作. 它使你可以在不改變各元素的類的前提下定義作用於這些元素的新操作.
訪問者模式適用於數據結構相對穩定的系統. 它把數據結構和作用於結構上的操作之間的耦合解脫開, 使得操作集合可以相對自由地演化.
訪問者模式的優點就是增加新的操作很容易, 因為增加新的操作就意味著增加一個新的訪問者. 訪問者模式將有關的行為集中到一個訪問者對象中.
訪問者模式的缺點是使增加新的數據結構變得困難了.