7.6 KiB
tags, aliases, date, time, description
| tags | aliases | date | time | description |
|---|---|---|---|---|
| 2022-05-26 | 15:12:04 |
策略模式(Strategy)
策略模式可以提供不一樣的演算法,但是又不用更改程式。 以常見的鴨子為例,有一個基礎類別Duck,如何衍生出會飛得鴨子跟不會飛的鴨子?抑或是會叫的跟不會叫的? 第一部是將會變動的部份分離出來,讓鴨子類別不需要去在乎飛跟叫的問題。 再來是把飛跟叫的部份包裝成另一個class,並以之為基礎類別來實做出「實際的類別」。 ^e59e9f
以一般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就可以動態的切換實作,也就是說你可以從設定檔來決定你的鴨子要長什麼樣子。
觀察者模式(Observer Pattern )
有一個會產生變動的主角(subject),與一堆需要觀察變動的觀察者(Observer)。觀察者向主角註冊,當主角發生變化的時候,發後通知給觀察者。
!
Subject方面,attach() 就是註冊觀察者,也可以叫做 register()、add() 之類。 detach() 用來移除用戶,也可以叫做 unregister()、remove() 之類。 notify()則是當發生變化時,用來通知所有觀察者的實作。
觀察者方面必須實作 update() 才能收到通知。
裝飾者模式(Decorator Pattern)
「裝飾者」通常與「被裝飾者」有同樣的界面,「裝飾者」會取代「被裝飾者」的界面,進而改變「被裝飾者」的行為。 裝飾者模式讓物件可以動態的改變行為,進為符合不同的需求。 以書上的例子來說,我們多種飲料,每種飲料都可以加上不同的配料。例如,有奶茶、綠茶、紅茶3種飲料,另外有珍珠、紅豆、綠豆、仙草4種配料,我們要如何設計出適合的類別來讓每種飲料都可以隨寄的搭配配料呢?
假設這樣寫:
- !!!col
-
1
Ingredient class
class IngredientBubble { ... }; class IngredientRedbean { ... }; class IngredientGreenbean { ... }; class IngredientFairyGrass { ... }; -
2
Beverage class
class Beverage { int32_t cost() { return cost; } int32_t cost; } class BeverageMilkTea { ... }; class BeverageGreenTea { ... }; class BeverageBlackTea { IngredientBubble* bubble; IngredientRedbean* redbean; IngredientGreenbean* greenbean; IngredientFairyGrass* fairyGrass; }
-
每個飲料的class裡面都將每個配料定義為一個member,如果客人有加配料的話,我們就將配料實例化,假設奶茶加了珍珠:
class BeverageBlackTea {
void addBubble() {
if (!bubble) bubble = new IngredientBubble();
}
};
要算價格的時候:
class BeverageBlackTea {
int32_t cost() {
if (bubble) cost += 10; // 珍珠要加10元
if (redbean) cost += 5; // 紅豆要加10元
if (greenbean) cost += 7; // 綠豆要加10元
if (fairyGrass) cost += 9; // 珍珠要加10元
return cost;
}
int32_t cost = 30; // 奶茶本身10元
};
這樣的問題是,每當有一種新配料出現,我們就要在奶茶類別裡修改至少2個function,addXXX()與cost(),目前我們有3種飲料,所以要修改6個function,更何況,如果客人要加2份珍珠怎麼辦?這明顯不利程式的維護,必須有一種方法讓程式的修改最小,讓寫好的程式不用被修改才行。
讓裝飾者模式來改善這個問題。首先讓配料跟飲料有同樣的界面,但是修改一下配料的constructor:

