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

