策略模式(Strategy)
定義一系列算法,把他們一個個封裝起來,並且是他們可以互相替換(變化)。該模式似的算法可以獨立於使用它的客戶程序(穩定的)而變化(擴展,子類化)。——《設計模式》GoF
上面部分是穩定的,下面是變化的。
動機(Motivation)
在軟件構建的過程中,某些對象使用的算法可能是多種多樣的,經常改變,如果將這些算法都編碼到對象中,將會使對象變得異常複雜;而且有時候支持不使用的算法也是一種性能負擔
比如現在需要有一個稅種的計算系統,其中支持計算各國的稅種,那麼他的僞碼描述就是如下:
enum TaxBase {
CN_Tax,
US_Tax,
DE_Tax,
};
class SalesOrder{
TaxBase tax;
public:
double CalculateTax(){
//...
if (tax == CN_Tax){
//CN***********
}
else if (tax == US_Tax){
//US***********
}
else if (tax == DE_Tax){
//DE***********
}
//....
}
};
但是隨着時間的推移,需要增加一個稅種的計算,僞碼會編程以下這樣了
enum TaxBase {
CN_Tax,
US_Tax,
DE_Tax,
FR_Tax //更改
};
class SalesOrder{
TaxBase tax;
public:
double CalculateTax(){
//...
if (tax == CN_Tax){
//CN***********
}
else if (tax == US_Tax){
//US***********
}
else if (tax == DE_Tax){
//DE***********
}
else if (tax == FR_Tax){ //更改
//...
}
//....
}
};
我們不但需要修改TaxBase中的參數,還需要在SalseOrder中添加關於新增加稅種的一個判斷,然後才能進行相應的計算。對於上面這個僞碼描述來說,修改的地方也是比較多的,基本上是牽一髮而動全身了。並且,在SalesOrder中,CalculatetTax函數中每次調用其實只會使用其中的一個分支,那麼其餘的分支每次也需要被加載到內存中去,其實也會是一種資源的浪費。
也就是他違反了對開閉原則,同時也違反了依賴倒置的原則。
解決辦法:
class TaxStrategy{
public:
virtual double Calculate(const Context& context)=0;
virtual ~TaxStrategy(){}
};
class CNTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
class USTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
class DETax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
class SalesOrder{
private:
TaxStrategy* strategy;
public:
SalesOrder(StrategyFactory* strategyFactory){
this->strategy = strategyFactory->NewStrategy();
}
~SalesOrder(){
delete this->strategy;
}
public double CalculateTax(){
//...
Context context();
double val =
strategy->Calculate(context); //多態調用
//...
}
};
那麼此時如果需要增加一個新的稅種,該如何增加呢?
class TaxStrategy{
public:
virtual double Calculate(const Context& context)=0;
virtual ~TaxStrategy(){}
};
class CNTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
class USTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
class DETax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//***********
}
};
//擴展
//*********************************
class FRTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
//.........
}
};
class SalesOrder{
private:
TaxStrategy* strategy;
public:
SalesOrder(StrategyFactory* strategyFactory){
this->strategy = strategyFactory->NewStrategy();
}
~SalesOrder(){
delete this->strategy;
}
public double CalculateTax(){
//...
Context context();
double val =
strategy->Calculate(context); //多態調用
//...
}
};
對於第二個方法來說,SalesOrder的CalculateTax函數來說,他多態調用了TaxStrategy類中的Calucate方法。而TaxStrategy來說他的每個子類都是一種新的稅種計算方式,每個稅種計算中還實現了自己對應的稅種計算的方法。在擴展的時候,需要增加TaxStrategy的子類就行了,不需要在改變SalesOrder的中的代碼了。
要點總結
- Strategy及其子類爲組件提供了一系列可重用的算法,從而可以使得類型在運行時方便的根據需要在各個算法之間進行切換
- Strategy模式提供了用條件判斷語句的另一種選擇,消除條件判斷語句,就是在解耦合。含有許多條件判斷語句的代碼通常都需要Strategy模式。尤其是條件判斷語句在未來會有增加可能性的時候,應該優先考慮Strategy模式。
- 如果Strategy對象沒有實例變量,那麼各個上下文可以共享同一個Strategy對象,從而節省對象的開銷。