設計模式-策略模式(Strategy Pattern) === ## 內容 這篇文章是讀完"Head Firt設計模式"第一章之後所寫的讀書紀錄。 書上用一句話說明了策略模式 > 將某一堆算法定義成一個算法族,分別封裝起來,讓他們之間可以互相替換,此模式讓算法的變化獨立於使用算法的物件。 直接以第一章的"設計謎題"為例: > 一個動作冒險遊戲中,有許多的角色種類以及許多的武器種類,每個角色一次只能使用一種武器,但是可以在遊戲過程中換武器。 首先既然有角色這個類別,那麼很明確的我們必須先寫一個基礎類別:Character。 ```cpp class Character { public: Character() {} ~Character() {} void fight() { useWeapon(); } virtual void useWeapon() { // 依照不同角色從新實現使用武器 } } ``` 這種作法會讓我們有一個非常難處理的狀況,假設有A跟B兩個角色同時要使用相同的武器,而且使用武器的動作也一模一樣,那們我們就必須要在這兩個衍生類別中重新實現一模一樣的算法,之後如果我們要更改這個動作的細節,那麼我們就必須要一一的改變使用相同算法的類別。 **這不是一個好的軟體開發方法,因為程式複用率太低了。** 而且用這種方法會將某個類別中的算法固定下來,這樣就無法達到我們需要的"可在運行期間改變物件的行為"。 因此我們必須先分析出這個問題中**易於變化的部份**,將這個易於變化的部份抽離出來封裝成一堆的"算法族"。在這個需求裡面易於變化的部份肯定就是"換武器"這個功能。那麼依照策略模式的概念,我們應該先建立一個`<<interface>>`出來讓這個算法族中的各個衍生類別來實做不同算法。 然而在C\++中是沒有interface的型態的,但是我們可以透過純虛函數(pure virtual function)來實做相近的概念,在Java中的interface是不能被實例化的,一個interface必須透過`implements`重新實現抽象的方法。在C\++中的純虛函數也是相近的概念,一個含有純虛函數的類別是不能被實例化的,也是必需透過繼承這個類別並且實做這個函數之後才能被實例化,此外,C\++中可以"多重繼承"恰巧也能實現Java中一個類別`implements`好幾個interface。 ```cpp class WeaponBehavior { public: WeaponBehavior() {} ~WeaponBehavior() {} virtual void useWeapon() = 0; } ``` 接著就可以在剛才的Charactor類別中加入一個成員: ```cpp class Character { public: Character(WeaponBehavior *weaponBehavior) : m_weaponBehavior(weaponBehavior) {} virtual ~Character() { delete m_weaponBehavior; m_weaponBehavior = nullptr; } void fight() { this->m_weaponBehavior->useWeapon(); } void setWeaponBehavior(WeaponBehavior *weaponBehavior) { this->m_weaponBehavior = weaponBehavior; } private: WeaponBehavior *m_weaponBehavior; } ``` 這樣就完成了基礎的策略模式基本類別建立,之後就可以透過繼承`Charactor`類別,來創建許多的衍生類別: ```cpp class King : public Character { public: King(WeaponBehavior *weaponBehavior) : Character(weaponBehavior) {} ~King() {} } class Queen : public Character { public: Queen(WeaponBehavior *weaponBehavior) : Character(weaponBehavior) {} ~Queen() {} } class Knight : public Character { public: Queen(WeaponBehavior *weaponBehavior) : Character(weaponBehavior) {} ~Queen() {} } ``` 之後透過繼承WeaponBehavior然後實現`useWeapon()`的函數,實現數個算法。而這些類別因為都是實現(implements)同一個interface因此他們能夠透過多型相互替換。 ```cpp class SwordBehavior : public WeaponBehavior { public: SwordBehavior() {} ~SwordBehavior() {} virtual void useWeapon() { // 砍擊 } } class ArrowAndBowBehavior : public WeaponBehavior { public: ArrowAndBowBehavior() {} ~ArrowAndBowBehavior() {} virtual void useWeapon() { // 射擊 } } class PikeBehavior : public WeaponBehavior { public: PikeBehavior() {} ~PikeBehavior() {} virtual void useWeapon() { // 刺擊 } } ``` 定義好一堆算法之後就可以來讓這個程式運行一下: ```cpp int main() { // Initialize objects King *king = new King(new SwordBehavior()); Knight *knight = new Knight(new PikeBehavior()); // Use weapons and fight. king->fight(); // 砍擊 knight->fight(); // 刺擊 // Change weapons king->setWeaponBehavior(new ArrowAndBowBehavior()); knight->setWeaponBehavior(new SwordBehavior()); // Use weapons and fight again. king->fight(); // 射擊 knight->fight(); // 砍擊 } ``` 首先實例化King類別建立一個king物件,且讓這個物件**有一個**`SwordBehavior`,然後再實例化Knight類別建立一個knight物件且讓這個物件也**有一個**`PikeBehavior`。 ### 這種設計方法的好處 因為在一個軟體生命週期中,我們並沒有辦法預期到未來我們的軟體將會如何增長,而如果我們採用**策略模式**來設計,那麼我們就可以相當放心的等待軟體的增長。 以上面的動作策略遊戲來說,如果我們要在這些角色中增加特殊能力(useSkill),那麼如果把這些特殊能力也定義成一個`SkillBehavior`算法族,並且透過將各種特殊能力封裝成各個算法,這樣就可以讓擁有相同特殊能力的角色複用這份程式,不用在擁有相同特殊能力的角色類別中重複實現相同的算法。此外還可以增加這些特殊能力的彈性,在執行的時候動態改變角色的特殊能力。 ```cpp class SkillBehavior { public: SkillBehavior() {} ~SkillBehavior() {} virtual void useSkill() = 0; } class MagicBehavior : public SkillBehavior { public: MagicBehavior() {} ~MagicBehavior() {} virtual void useSkill() { // 施展魔法 } } class Character { public: Character(WeaponBehavior *weaponBehavior, SkillBehavior *skillBehavior) : m_weaponBehavior(weaponBehavior) , m_skillBehavior(skillBehavior) {} virtual ~Character() { delete m_weaponBehavior; m_weaponBehavior = nullptr; } void fight() { this->m_weaponBehavior->useWeapon(); } void setWeaponBehavior(WeaponBehavior *weaponBehavior) { this->m_weaponBehavior = weaponBehavior; } void setSkillBehavior(SkillBehavior *skillBehavior) { this->m_skillBehavior = skillBehavior; } private: WeaponBehavior *m_weaponBehavior; SkillBehavior *m_skillBehavior; } ``` ## 結論 策略模式就是將容易變化的部份從繼承的關係中抽出來,因為繼承關係中,若基礎類別的其中一個部份被修改了那麼則會牽一髮動全身,況且如果採用純繼承完成這個部份的話,程式的複用率降得很低,而且還不能在運行時切換算法。 繼承關係其實是一個"IS-A"(是一個)的概念,比如說上述的例子: + King is a character. + Knight is a character. 而策略模式就是將"IS-A"(是一個)的關係改變為"HAS-A"(有一個)的關係: + King has a SwordBehavior. + Knight has a PikeBehavior. 當這個武器的行為被改為"有一個"之後,就能很直觀的認為他是一個可動態抽換的部份。只要被抽換以及替代的那個算法能夠是相同算法族的算法即可。