134 lines
4.6 KiB
Markdown
134 lines
4.6 KiB
Markdown
---
|
||
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就可以動態的切換實作,也就是說你可以從設定檔來決定你的鴨子要長什麼樣子。
|