--- tags: aliases: date: 2022-05-26 time: 15:12:04 description: --- ## 策略模式(Strategy) 策略模式可以提供不一樣的演算法,但是又不用更改程式。 以常見的鴨子為例,有一個基礎類別Duck,如何衍生出會飛得鴨子跟不會飛的鴨子?抑或是會叫的跟不會叫的? 第一部是將會變動的部份分離出來,讓鴨子類別不需要去在乎飛跟叫的問題。 再來是把飛跟叫的部份包裝成另一個class,並以之為基礎類別來實做出「實際的類別」。 以一般C++的override方法,會用的方式大致是這樣: - !!!col - 1 ### 基礎類別 ```cpp class duck { duck() {} void fly() {} void makeSound() {} void run() {} } ``` - 1 ### 衍生類別,遙控鴨子,會飛不會叫 ```cpp class duckRC : public duck { duckRC() {} void fly() { printf("I can fly"); } void makeSound() { printf("I cannot make sound!"); } } ``` - 1 ### 衍生類別,木頭鴨子,不會飛不會叫 ```cpp class duckWood : public duck { duckWood() {} void fly() { printf("I cannot fly"); } void makeSound() { printf("I cannot make sound!"); } } ``` 但是這樣的話如果基礎類別會更改的話,那麼子類別也全部都會受影響,例如,現在希望所有的鴨子都要有一個「跑」的功能,所以我們就在基礎類別裡加一個`run()`: ```cpp class duck { void fly() {} void makeSound() {} void run() { printf("I can run"); } } ``` 結果現在木頭鴨子也能跑了,這不符合我們的設計,所以我們必須回頭更改木頭鴨子的程式,改成 ```cpp class duckWood : public duck { void fly() { printf("I cannot fly"); } void makeSound() { printf("I cannot make sound!"); } void run() { printf("I cannot run!"); } // <- 要改! } ``` 如果我們類別很多,那麼就很可能有沒改到的漏網之魚,bug就發生了。 ### 封裝變動的部份 比較好的方法一旦類別寫完之後,我們就不要動它了,日後新增的功能也不可以影響到他。我們把「會變動」的部份分離出來,變成各自的類別。 為了簡化,我們討論「飛」這個功能就好,「飛」只分成「會飛」跟「不會飛」2種類別: - !!!col - 1 ### 基礎類別,IFly ```cpp class IFly { void doFly() = 0; } ``` - 1 ### 衍生類別,CanFly ```cpp class CanFly { void doFly() { printf("I can fly"); } } ``` - 1 ### 衍生類別,CannotFly ```cpp class CannotFly { void doFly() { printf("I cannot fly"); } } ``` 回到鴨子的基礎類別這邊,基礎類別`duck`本來是直接擁有`fly()`這個member function,現在我們把他改成他擁有的是`IFly`: ```cpp class duck { duck() {} fly() { fly->doFly() }; IFly* fly = nullptr; ... } ``` 重寫遙控鴨子這個衍生類別: ```cpp class duckRC : public duck { duckRC() { this->fly = new CanFly(); } ... } ``` 重寫木頭鴨子,不會飛,所以: ```cpp class duckWood : public duck { duckWood() { this->fly = new CannotFly(); } } ``` 現在,不管是遙控鴨子或是木頭鴨子,在被呼叫`fly()`的時候,都是根據它fly的「實際類別」來動作,遙控鴨子的`fly()`呼叫的是`CanFly`的`doFly()`,木頭鴨子的`fly()`呼叫的是`CannotFly`的`doFly()`,它們的動作完全取決於他們初始化的方式,而他們初始化的方式則取決於你的產品定義,要是今天你需要不同的飛行方式,增加新的fly類別即可,不會影響到舊有的。要是鴨子基礎類別增加了新的行為,如run,那麼就幫鴨子基礎類別的`run()`加個什麼都不做的預設動作就好,反正舊有的鴨子衍生類別本來就對這個新行為沒反應。要是`CanFly`的定義改變了,那麼你就是改變了使用`CanFly`的所有類別,這是你的定義明確的改變了,不是有程式被「額外」的改變了。 - !!!col - 1 ### 原本直接繼承的方式 ![[Pasted image 20220526182952.png]] - 2 ### 封裝變動的部份 ![[Pasted image 20220526183019.png]] 這樣做的另一個好處是fly的初始化是動態的,只要再多一個`set()` function就可以動態的切換實作,也就是說你可以從設定檔來決定你的鴨子要長什麼樣子。