摘自<<Head First Design Patterns>> chapter 1 1 飛翔的鴨子 假設開發一款模擬鴨子的游戲。首先設計一個鴨子母類,裡面有鴨子的叫聲、游泳和外形三個成員函數,然後在野鴨和紅頭鴨兩個子類中重寫繼承的外形函數。 現在更進一步,要求鴨子會飛,應該如何設計程
摘自<<Head First Design Patterns>> chapter 1
1 飛翔的鴨子
假設開發一款模擬鴨子的游戲。首先設計一個鴨子母類,裡面有鴨子的叫聲、游泳和外形三個成員函數,然後在野鴨和紅頭鴨兩個子類中重寫繼承的外形函數。
現在更進一步,要求鴨子會飛,應該如何設計程式呢?
2 兩種方法
1) 繼承
為 Duck 母類添加 fly() 成員函數,然後各個子類繼承 fly()
問題: 並不是所有的鴨子都會飛,比如“煮熟的鴨子”。
解決: 不會飛的鴨子自母類繼承 fly() 後,重寫該函數。
因為鴨子會不會飛的問題,又發現一個bug,並不是所有的鴨子都會叫,比如“大黃鴨”。因此,不同的子類也需要重寫 quack()
此時問題又來了,公司開發的是游戲軟體,要求游戲每三個月更新一次,每次更新後鴨子的飛行方式和叫聲都可能發生新的變化。那麼,豈不是每三個月都要重寫 RubberDuck 和 DecoyDuck 子類甚至更多子類的 fly() 和 quack() 函數,有點繁瑣。
於是,有了第二個方法...
2) 介面 (interface)
視會飛和會叫為一種能力,並將 Flyable 和 Quackable 做成介面 (interface),然後在裡面加上相應的函數 fly() 和 quack()
這樣,只有會飛的子類實現該介面 Flyable, 並且具有 fly() 函數。同理只有會叫的子類實現介面 Quackable 且有 quack() 函數。
問題又來了,飛行是鴨子的一種行為,不同的鴨子,其各自飛行的方式也不相同。
很顯然,每個子類中還得重寫 fly() 和 quack()。因此,單純的使用介面或繼承都不是最佳的方法。
那麼,到底什麼方法能完美解決鴨子 fly 和 quack 的問題呢?不著急,先看下麵的三個設計原則。
3 設計原則
1) identify what varies and separate them from what stays the same
鴨子類中, 很明顯“飛”和“叫”是變化的,於是先把 fly 和 quack 擇出來,和其它不變的分隔開來。
2) program to an interface, not an implementation
3) favor composition over inheritance
HAS-A better than IS-A
4 策略模式
現在引出策略模式
defines a family of algorithms, encapsulates each one, and makes them interchangeable.
(Strategy lets the algorithm vary independently from clients that use it)
1) client
Java: 在 Duck 母類中定義 FlyBehavior 和 QuackBehavior 的成員對象 (FlyBehavior flyBehavior)
C++: 在 Duck 母類中定義 FlyBehavior 和 QuackBehavior 的指針 (FlyBehavior * pflyBehavior)
2) encapsulated fly behavior
把飛行封裝為介面,裡面具體實現不同的飛行方式,即將各種”飛行方式“視為一系列”演算法“,添加或者修改演算法都在這個介面裡面進行。
這樣,不同的鴨子子類,只需通過母類中的成員對象 (flyBehavior) 調用相應的”飛行演算法“即可。
3) encapsulated quack behavior
Java: 直接使用關鍵字 interface 便可將 QuackBehavior 定義成介面
C++: 介面 ≈ 抽象基類,將 QuackBehavior 定義為抽象基類,也即聲明 quack() 為純虛函數, 具體代碼形式為 “void quack() = 0”