一 定義
策略模式是指定義一系列的算法,把它們一個個封裝起來,並且使它們可相互替換。本模式使得算法可獨立於使用它的客戶而變化。也就是說這些算法所完成的功能一樣,對外的接口一樣,只是各自實現上存在差異。
策略模式把對象本身和運算規則區分開來,其功能非常強大,因爲這個設計模式本身的核心思想就是面向對象編程的多形性的思想。
二 ULM圖
環境類(Context):用一個ConcreteStrategy對象來配置。維護一個對Strategy對象的引用。可定義一個接口來讓Strategy訪問它的數據。
抽象策略類(Strategy):定義所有支持的算法的公共接口。 Context使用這個接口來調用某ConcreteStrategy定義的算法。
具體策略類(ConcreteStrategy):以Strategy接口實現某具體算法。
三 策略模式的優點和缺點及可用場景
1、策略模式的優點
(1)策略模式是一種定義一系列算法的方法,從概念上來看,所有這些算法完成的都是相同的工作,只是實現不同,他可以以相同的方式調用所有的算法,減少了各種算法類與使用算法類之間的耦合。
(2)策略模式的Strategy類曾是爲Context定義了一些列的可供重用的算法或行爲。集成有助於析取出這些算法中的公共功能。
(3)策略模式簡化了單元測試,因爲每個算法都有自己的類,可以通過自己的接口單獨測試。
(4)策略模式就是用來封裝算法的。
(5)只要在分析過程中聽到需要在不同時間應用不同的業務規則,就可以考慮使用策略模式處理這種變化的可能性。
(6)簡單工廠模式需要讓客戶端認識兩個類,而策略模式和簡單工廠模式結合的用法,客戶端只需要認識一個類Context即可。
2、策略模式的缺點
- 客戶端必須知道所有的策略類,並自行決定使用哪一個策略類: 本模式有一個潛在的缺點,就是一個客戶要選擇一個合適的Strategy就必須知道這些Strategy到底有何不同。此時可能不得不向客戶暴露具體的實現問題。因此僅當這些不同行爲變體與客戶相關的行爲時 , 才需要使用Strategy模式。
- Strategy和Context之間的通信開銷 :無論各個ConcreteStrategy實現的算法是簡單還是複雜, 它們都共享Strategy定義的接口。因此很可能某些 ConcreteStrategy不會都用到所有通過這個接口傳遞給它們的信息;簡單的 ConcreteStrategy可能不使用其中的任何信息!這就意味着有時Context會創建和初始化一些永遠不會用到的參數。如果存在這樣問題 , 那麼將需要在Strategy和Context之間更進行緊密的耦合。
- 策略模式將造成產生很多策略類:可以通過使用享元模式在一定程度上減少對象的數量。 增加了對象的數目 Strategy增加了一個應用中的對象的數目。有時你可以將 Strategy實現爲可供各Context共享的無狀態的對象來減少這一開銷。任何其餘的狀態都由 Context維護。Context在每一次對Strategy對象的請求中都將這個狀態傳遞過去。共享的 Strategy不應在各次調用之間維護狀態。
3、策略模式的可用場景及注意事項
- 當一組算法對應一個任務,並且程序可以在運行時靈活的選擇其中一個算法,策略模式是很好的選擇。
- 需要安全的封裝平等的多種不同類型操作
- 當同一抽象類擁有很多具體子類時且程序運行的時候需要動態決定使用哪一具體類時
- 策略模式的重心不是如何實現算法,而是關注如何組織、調用這些算法,從而讓程序結構更靈活,具有更好的維護性和擴展性。
- 策略模式中各個策略算法的平等性。對於一系列具體的策略算法,大家的地位是完全一樣的,正因爲這個平等性,才能實現算法之間可以相互替換。所有的策略算法在實現上也是相互獨立的,相互之間是沒有依賴的。
- 運行時策略的互斥性,策略模式在運行的每一個時刻只能使用這組被封裝算法中的某一個,雖然可以動態地在不同的策略實現中切換,但是同時只能使用一個。
- 策略的抽象實現,當所有的具體策略類都有一些公有的行爲。此時根據設計原則應把這些公有的行爲放到共同的抽象策略角色Strategy類裏面。
實例
1. 高速緩存(Cache)算法
什麼是Cache的替換算法呢?簡單解釋一下, 當發生Cache缺失時,Cache控制器必須選擇Cache中的一行,並用欲獲得的數據來替換它。所採用的選擇策略就是Cache的替換算法。下面給出相應的UML圖。
ReplaceAlgorithm是一個抽象類,定義了算法的接口,有三個類繼承自這個抽象類,也就是具體的算法實現。Cache類中需要使用替換算法,因此維護了一個 ReplaceAlgorithm的對象。這個UML圖的結構就是策略模式的典型結構。下面根據UML圖,給出相應的實現。
首先給出替換算法的定義。
//抽象接口
class ReplaceAlgorithm
{
public:
virtual void Replace() = 0;
};
//三種具體的替換算法
class LRU_ReplaceAlgorithm : public ReplaceAlgorithm
{
public:
void Replace() { cout<<"Least Recently Used replace algorithm"<<endl; }
};
class FIFO_ReplaceAlgorithm : public ReplaceAlgorithm
{
public:
void Replace() { cout<<"First in First out replace algorithm"<<endl; }
};
class Random_ReplaceAlgorithm: public ReplaceAlgorithm
{
public:
void Replace() { cout<<"Random replace algorithm"<<endl; }
};
接着給出Cache的定義,這裏很關鍵,Cache的實現方式直接影響了客戶的使用方式,其關鍵在於如何指定替換算法。
//Cache需要用到替換算法
template <class RA>
class Cache
{
private:
RA m_ra;
public:
Cache() { }
~Cache() { }
void Replace() { m_ra.Replace(); }
};
int main()
{
Cache<Random_ReplaceAlgorithm> cache; //模板實參
cache.Replace();
return 0;
}
2. 商店促銷
#include <iostream>
#include <string>
#include <cmath>
#include <memory>
//父類抽象類
class CashSuper
{
public:
virtual double acceptCash(double) = 0;
virtual ~CashSuper() = default;
};
//子類:正常付費類型
class CashNormal:public CashSuper
{
public:
double acceptCash(double money)
{
return money;
}
};
//子類:返現類型
class CashReturn:public CashSuper
{
private:
double moneyCondition;
double moneyReturn;
public:
CashReturn(double moneyCondition,double moneyReturn)
{
this->moneyCondition=moneyCondition;
this->moneyReturn=moneyReturn;
}
double acceptCash(double money)
{
double result=money;
if(money>moneyCondition)
result=money - std::floor(money/moneyCondition)*moneyReturn;
return result;
}
};
//子類:打折扣類型
class CashRebate:public CashSuper
{
private:
double moneyRebate;
public:
CashRebate(double moneyRebate)
{
this->moneyRebate=moneyRebate;
}
double acceptCash(double money)
{
return money*moneyRebate;
}
};
enum SellStrategy
{
normal,
returnMoney,
rebate
};
class CashContext
{
private:
std::unique_ptr<CashSuper> cs;
public:
CashContext(int type)
{
switch(type)
{
case SellStrategy::normal:
{
cs = std::make_unique<CashNormal>();
break;
}
case SellStrategy::returnMoney:
{
cs = std::make_unique<CashReturn>(300,100);
break;
}
case SellStrategy::rebate:
{
cs = std::make_unique<CashRebate>(0.8);
break;
}
default:;
}
}
double GetResult(double money)
{
return cs->acceptCash(money);
}
};
int main(void)
{
double total=0;
double totalPrices=0;
//正常收費
auto cc1 = std::make_unique<CashContext>(SellStrategy::normal);
totalPrices=cc1->GetResult(300);
total+=totalPrices;
std::cout<<"Type:正常收費 totalPrices:"<<totalPrices<<" total:"<<total<<std::endl;
totalPrices=0;
//返現類型
auto cc2 = std::make_unique<CashContext>(SellStrategy::returnMoney);
totalPrices=cc2->GetResult(700);
total+=totalPrices;
std::cout<<"Type:滿300返100 totalPrices:"<<totalPrices<<" total:"<<total<<std::endl;
totalPrices=0;
//打折類型
auto cc3 = std::make_unique<CashContext>(SellStrategy::rebate);
totalPrices=cc3->GetResult(300);
total+=totalPrices;
std::cout<<"Type:打8折 totalPrices:"<<totalPrices<<" total:"<<total<<std::endl;
totalPrices=0;
return 0;
}
運行結果如下:
與其他模式
1)模板模式
Strategy模式和Template模式實際是實現一個抽象接口的兩種方式:繼承和組合之間的區別。要實現一個抽象接口,繼承是一種方式:我們將抽象接口聲明在基類中,將具體的實現放在具體子類中。組合(委託)是另外一種方式:我們將接口的實現放在被組合對象中,將抽象接口放在組合類中。
面向對象的設計中的有一條很重要的原則就是:優先使用(對象)組合,而非(類)繼承(Favor Composition Over Inheritance)。
2)狀態模式
策略模式和其它許多設計模式比較起來是非常類似的。策略模式和狀態模式最大的區別就是策略模式只是的條件選擇只執行一次,而狀態模式是隨着實例參數(對象實例的狀態)的改變不停地更改執行模式。換句話說,策略模式只是在
對象初始化的時候更改執行模式,而狀態模式是根據對象實例的週期時間而動態地改變對象實例的執行模式。
- 可以通過環境類狀態的個數來決定是使用策略模式還是狀態模式。
- 策略模式的環境類自己選擇一個具體策略類,具體策略類無須關心環境類;而狀態模式的環境類由於外在因素需要放進一個具體狀態中,以便通過其方法實現狀態的切換,因此環境類和狀態類之間存在一種雙向的關聯關係。
- 使用策略模式時,客戶端需要知道所選的具體策略是哪一個,而使用狀態模式時,客戶端無須關心具體狀態,環境類的狀態會根據用戶的操作自動轉換。
- 如果系統中某個類的對象存在多種狀態,不同狀態下行爲有差異,而且這些狀態之間可以發生轉換時使用狀態模式;
- 如果系統中某個類的某一行爲存在多種實現方式,而且這些實現方式可以互換時使用策略模式。
3)簡單工廠模式的區別
工廠模式是創建型模式 ,它關注對象創建,提供創建對象的接口. 讓對象的創建與具體的使用客戶無關。
策略模式是對象行爲型模式 ,它關注行爲和算法的封裝 。它定義一系列的算法,把每一個算法封裝起來, 並且使它們可相互替換。使得算法可獨立於使用它的客戶而變化。
總結與分析
1)策略模式是一個比較容易理解和使用的設計模式,策略模式是對算法的封裝,它把算法的責任和算法本身分割開,委派給不同的對象管理。策略模式通常把一個系列的算法封裝到一系列的策略類裏面,作爲一個抽象策略類的子類。用一句話來說,就是“準備一組算法,並將每一個算法封裝起來,使得它們可以互換”。
2)在策略模式中,應當由客戶端自己決定在什麼情況下使用什麼具體策略角色。
3)策略模式僅僅封裝算法,提供新算法插入到已有系統中,以及老算法從系統中“退休”的方便,策略模式並不決定在何時使用何種算法,算法的選擇由客戶端來決定。這在一定程度上提高了系統的靈活性,但是客戶端需要理解所有具體策略類之間的區別,以便選擇合適的算法,這也是策略模式的缺點之一,在一定程度上增加了客戶端的使用難度。