深入淺出設計模式C++實現(1)——策略模式
來自專欄深入淺出設計模式——C++實現
說明
本系列為Head First設計模式的C++實現,中間會夾帶作者的不成熟見解。
由於本人只是不滿24歲的普通學生,知識有限,有錯誤希望能指出。
寫這個最主要的原因還是找不到工作,空餘時間太多,給自己找點事做。所以如果能收到offer的話估計就鴿了。
至於為什麼選擇設計模式,還是因為有趣吧,總比寫什麼操作系統內核要好。
策略模式
策略模式是全書提到的第一種模式,我的理解就是通過組合來實現多態。
舉書中的例子來說,現在需要去設計實現多種鴨子對象。鴨子有許多種,如野鴨、橡皮鴨;有多種方法,可以fly,可以quack,可以display。
總體來看,肯定要用繼承。單獨為每種鴨子編寫類的話,在具體處理鴨子對象之時會有很多不便,比如一個方法,接收鴨子對象做參數的話就得為所有類重載一個版本。繼承的話就要考慮什麼父類和子類的類容。
父類中應該包含鴨子都具有的功能,這樣編碼時可以全部當作duck類處理。那麼也就是fly、quack、display三個功能。這三個還是存在不同的。display用於顯示該鴨子的具體描述,具體的子類效果都不一樣,而fly、quack總體來說可以分為幾類,如會飛、不會飛、會叫、不會叫,每一個子類可以不同,也可以相同。
display很明顯,父類應該聲明為純虛函數,每一子類具體實現。而對於fly和quack來說,這樣做會有問題。首先是重複編碼,相同的功能代碼都是相同的;其次是如果需要修改,那麼就需要修改所有的具體實現,一致性難以確保。
繼承不行,那如果用介面呢?會飛的就實現一個飛行介面,會叫的就實現一個叫的介面。這樣處理仍然會有代碼無法復用的問題,子類必須包含具體實現,同樣難以保證一致性。
書中提供的解決方法是將fly和quack從duck類中提取出來,設計出FlyBehavior和QuackBehavior類,並通過繼承具體實現,之後將具體實現綁定到具體的鴨子類上。這樣就典型的是C++中多態的一個運用。duck基類中包含FlyBehavior和QuackBehavior的指針,具體的鴨子繼承duck後將這兩個指針指向具體實現。
代碼實現
代碼地址
實際去寫的時候發現C++還是博大精深,各種地方都要注意,很容易掉坑裡。
class FlyBehavior{public: virtual void fly() = 0;};class FlyWithWings :public FlyBehavior{public: void fly() { cout << "Im flying" << endl; }};
這是FlyBehavior類的設計。需要注意的是FlyWithWings需要public繼承。不加的話默認是private繼承。這時將不能通過基類的指針指向子類,具體的細節我就不懂了。
Quack類設計類似,就省略吧。
class Duck{private: FlyBehavior* flyBehavior; QuackBehavior* quackBehavior;public: Duck() { flyBehavior = NULL; quackBehavior = NULL; } ~Duck() { delete flyBehavior; delete quackBehavior; } void performFly() { if (flyBehavior != NULL) flyBehavior->fly(); else cout << "fly behavior is null" << endl; } void performQuack() { if (quackBehavior != NULL) quackBehavior->quack(); else cout << "quack behavior is null" << endl; } virtual void display() = 0; void swim() { cout << "All ducks float, even decoys" << endl; } void setFlyBehavior(FlyBehavior* fb) { delete flyBehavior; flyBehavior = fb; } void setQuackBehavior(QuackBehavior* qb) { delete quackBehavior; quackBehavior = qb; }};class MallardDuck :public Duck{public: MallardDuck() { setFlyBehavior(new FlyWithWings()); setQuackBehavior(new Quack()); } void display() { cout << "I am a real Mallard duck" << endl; }};
duck類中包含FlyBehavior和QuackBehavior的指針,子類在繼承後會將其綁定到具體實現上。同時開發set方法,可以實現運行時對方法的修改。
Duck* mallard = new MallardDuck();mallard->display();mallard->performFly();mallard->performQuack();mallard->setFlyBehavior(new FlyNoWay());mallard->performFly();
測試代碼運行也OK。
最好別看
接下來就是我的一存了。
這種寫法對每個鴨子對象都要去構造一個FlyBehavior和QuackBehavior對象,但實際上有用的只有那麼幾種。也許可以為每個FlyBehavior建立一個對象,所有的鴨子子類都包含對應對象的引用,這樣運行時只會有FlyBehavior具體實現只會有一個對象存在。免去析構的煩惱。
程序規模大了以後,例如在RPG遊戲中,攻擊方式、技能很多都是一樣的,可以構建一個類統一管理。管理類包含了所有具體實現的一個實例對象,可以給實現一個編號,開放對應的介面,根據編號調用對應的實現。然後主角或者怪物類中就存儲著編號,調用時就將編號傳遞給管理類。這樣改變起來也很方便,只用換個編號就可以了。
問題應該在於很多情況,這些攻擊或者技能可能不是和主角類完全無關的,需要用到主角的參數,這些參數估計就要用在調用時傳入,可能這些攻擊實現的代碼規模也會跟著擴大。當然這是我口嗨,因為沒寫過。
推薦閱讀:
※《遊戲設計模式》(遊戲編程模式)全書筆記+Unity實現
※設計模式奏鳴曲(一):開篇
※大白話設計模式 - 適配器模式
※如何掌握高級React設計模式: 複合組件【譯】
TAG:設計模式 |