一、 策略模式動機
- 軟件構建中,某些對象算法可能多種多樣,經常改變,如果將它們都編碼到對象中,會使得對象非常複雜,有時支持不適用的算法也會造成性能負擔;
- 如何在運行時透明地更改對象的算法,將算法與對象本身解耦,從而避免上述問題?
二、策略模式定義( GOF定義)
定義一系列算法,把他們一個個封裝起來,並且使他們可互相替換(變化)。該模式使得算法可獨立於使用他們的客戶程序(穩定)而變化.
三、代碼示例
3.1 重構前代碼
#include <stdio.h>
enum TaxBase // 稅種的枚舉體
{
CN_Tax = 0,
US_Tax = 1,
DE_Tax = 2
};
class SalesOrder
{
public:
SalesOrder(TaxBase tax) { this->tax = tax; }
~SalesOrder() {}
public:
double CalculateTax()
{ // 根據稅種類不同,計算稅額,不同國家算法不同
if (tax == CN_Tax) {} // 情況1...
else if (tax == US_Tax) {} // 情況2...
else if (tax == DE_Tax) {} // 情況3...
return 0;
}
private:
TaxBase tax;
};
int main()
{
SalesOrder sales_order(TaxBase::CN_Tax);
sales_order.CalculateTax();
renturn 0;
}
3.2 問題思考
程序咋一看沒什麼問題,但是否考慮到程序的後續可擴展性?比如增加一些國家(枚舉體),也會增加相應的算法;
違背原則:多擴展開放、對修改封閉. 本示例代碼中,如將來再實現其它國家稅率計算的擴展,doubleCalculateTax()的代碼就需要跟着相應變化.
3.3 重構後代碼
#include <stdio.h>
// 定義基類,抽象稅率計算統一的格式
class TaxStrategy {
public:
virtual double CalculateTax(const Context& contex) = 0; // 純虛方法
virtual ~TaxStrategy() {} // 良好的習慣,基類析構都寫成虛函數
};
// 添加擴展1:中國稅率計算類
class CNTax :public TaxStrategy {
public:
virtual double CalculateTax(const Context& contex) { // 實現接口類的純虛函數
// Override:實現China的算法
return 0;
}
};
// 添加擴展2:美國稅率計算類
class USTax :public TaxStrategy {
public:
virtual double CalculateTax(const Context& contex) { // 實現接口類的純虛函數
// Override:實現US的算法
return 0;
}
};
// 添加擴展3:德國稅率計算類
class DETax :public TaxStrategy {
public:
virtual double CalculateTax(const Context& contex) { // 實現接口類的純虛函數
// Override:實現DE的算法
return 0;
}
};
// 這裏,還可以添加任何擴展
// 即使增加了國家,SalesOrder類也不需要發生任何變化
class SalesOrder
{
public:
SalesOrder(StrategyFactory* strategyFactory){
this->strategy = strategyFactory->NewStrategy();
}
~SalesOrder() { delete this->strategy; }
public:
double CalculateTax()
{
Context contex;
double val = strategy->CalculateTax(contex);
return val;
}
private:
TaxStrategy* strategy; // 稅率策略,這是一個多態指針,如果放一個對象就沒有多態性了
};
// 工廠類模式將再後面的章節講解,這裏暫時先認爲它是一個產生抽象對象的工具
class StrategyFactory
{
public:
StrategyFactory() {}
~StrategyFactory() {}
public:
TaxStrategy* NewStrategy() {
// 後面講解工廠模式的時候再添加,這裏是策略模式的重點,怎麼在二進制級別複用,如不停機擴展
}
};
// 真正的複用指的是二進制意義的複用,而不是源代碼級別的複用
int main()
{
StrategyFactory* strategyFactory;
SalesOrder2 sales_order2(strategyFactory);
sales_order2.CalculateTax();
return 0;
}
四、策略模式要點總結
- Strategy及子類爲組件提供了一些列可重用的算法,從而使得類型在運行時方便根據需要在各算法之間切換;
- 策略模式提供了用條件判斷語句以外的一種可擴展的選擇,消除條件語句的耦合性,含有多個條件分支的代碼通常都需要策略模式;
- 如果策略對象沒有實例變量,那麼各個上下文可共享一個策略對象,從而節省對象開銷.
五、 思路總結
- 使用策略模式重構if語句的情形,在於if的情形始終存在變化的情形;
- 如果if分支的情況永遠不變,則if分支即可; Case1
- 如果if分支不變,但在某些情形下某些分支很大概率不會被執行,這是可能造成性能負擔,這是也可以使用策略模式. Case2