第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), 表示一個作用於某物件結構中的各元素的操作. 它使你可以在不改變各元素的類的前提下定義作用於這些元素的新操作.
訪問者模式適用於資料結構相對穩定的系統. 它把資料結構和作用於結構上的操作之間的耦合解脫開, 使得操作集合可以相對自由地演化.
訪問者模式的優點就是增加新的操作很容易, 因為增加新的操作就意味著增加一個新的訪問者. 訪問者模式將有關的行為集中到一個訪問者物件中.
訪問者模式的缺點是使增加新的資料結構變得困難了.