Files
Obsidian-Main/02. PARA/03. Resources(資源)/Design Pattern.md
2022-06-02 17:55:14 +08:00

134 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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就可以動態的切換實作也就是說你可以從設定檔來決定你的鴨子要長什麼樣子。