模式動機
完成一項任務,往往可以有多種不同的方式,每一種方式稱爲一個策略,我們可以根據環境或者條件的不同選擇不同的策略來完成該項任務。
在軟件開發中也常常遇到類似的情況,實現某一個功能有多個途徑,此時可以使用一種設計模式來使得系統可以靈活地選擇解決途徑,也能夠方便地增加新的解決途徑。
在軟件系統中,有許多算法可以實現某一功能,如查找、排序等,一種常用的方法是硬編碼(Hard Coding)在一個類中,如需要提供多種查找算法,可以將這些算法寫到一個類中,在該類中提供多個方法,每一個方法對應一個具體的查找算法;當然也可以將這些查找算法封裝在一個統一的方法中,通過if…else…等條件判斷語句來進行選擇。這兩種實現方法我們都可以稱之爲硬編碼,如果需要增加一種新的查找算法,需要修改封裝算法類的源代碼;更換查找算法,也需要修改客戶端調用代碼。在這個算法類中封裝了大量查找算法,該類代碼將較複雜,維護較爲困難。
除了提供專門的查找算法類之外,還可以在客戶端程序中直接包含算法代碼,這種做法更不可取,將導致客戶端程序龐大而且難以維護,如果存在大量可供選擇的算法時問題將變得更加嚴重。
爲了解決這些問題,可以定義一些獨立的類來封裝不同的算法,每一個類封裝一個具體的算法,在這裏,每一個封裝算法的類我們都可以稱之爲策略(Strategy),爲了保證這些策略的一致性,一般會用一個抽象的策略類來做算法的定義,而具體每種算法則對應於一個具體策略類。
根據上下文的不同靈活的切換算法。
策略模式(Strategy Pattern)
策略模式包含如下角色:
- Context: 環境類
- Strategy: 抽象策略類
- ConcreteStrategy: 具體策略類
策略模式是一個比較容易理解和使用的設計模式,策略模式是對算法的封裝,它把算法的責任和算法本身分割開,委派給不同的對象管理。策略模式通常把一個系列的算法封裝到一系列的策略類裏面,作爲一個抽象策略類的子類。用一句話來說,就是“準備一組算法,並將每一個算法封裝起來,使得它們可以互換”。
在策略模式中,應當由客戶端自己決定在什麼情況下使用什麼具體策略角色。
策略模式僅僅封裝算法,提供新算法插入到已有系統中,以及老算法從系統中“退休”的方便,策略模式並不決定在何時使用何種算法,算法的選擇由客戶端來決定。這在一定程度上提高了系統的靈活性,但是客戶端需要理解所有具體策略類之間的區別,以便選擇合適的算法,這也是策略模式的缺點之一,在一定程度上增加了客戶端的使用難度。
策略模式的優點
- 策略模式提供了對“開閉原則”的完美支持,用戶可以在不修改原有系統的基礎上選擇算法或行爲,也可以靈活地增加新的算法或行爲。
- 策略模式提供了管理相關的算法族的辦法。
- 策略模式提供了可以替換繼承關係的辦法。
- 使用策略模式可以避免使用多重條件轉移語句。
策略模式的缺點
- 客戶端必須知道所有的策略類,並自行決定使用哪一個策略類。
- 策略模式將造成產生很多策略類,可以通過使用享元模式在一定程度上減少對象的數量。
適用環境
在以下情況下可以使用策略模式:
- 如果在一個系統裏面有許多類,它們之間的區別僅在於它們的行爲,那麼使用策略模式可以動態地讓一個對象在許多行爲中選擇一種行爲。
- 一個系統需要動態地在幾種算法中選擇一種。
- 如果一個對象有很多的行爲,如果不用恰當的模式,這些行爲就只好使用多重的條件選擇語句來實現。
- 不希望客戶端知道複雜的、與算法相關的數據結構,在具體策略類中封裝算法和相關的數據結構,提高算法的保密性與安全性。
代碼示例
#include <iostream>
namespace strategy_pattern{
class strategy {
public:
virtual void algorithm() = 0;
};// class strategy
class concrete_strategy_A : public strategy {
public:
void algorithm() override {
std::cout << "concrete_strategy_A" << std::endl;
}
};// class concrete_strategy_A
class concrete_strategy_B : public strategy {
public:
void algorithm() override {
std::cout << "concrete_strategy_B" << std::endl;
}
};// class concrete_strategy_B
class context {
public:
context() : strategy_(nullptr) {}
void algorithm() {
assert(strategy_);
return strategy_->algorithm();
}
void set_strategy(strategy *strategy_arg) {
this->strategy_ = strategy_arg;
}
private:
strategy *strategy_;
};// class context
} // namespace strategy_pattern
int main() {
strategy_pattern::context _context;
strategy_pattern::concrete_strategy_A _concrete_strategy_A;
strategy_pattern::concrete_strategy_B _concrete_strategy_B;
_context.set_strategy(&_concrete_strategy_A);
_context.algorithm();
_context.set_strategy(&_concrete_strategy_B);
_context.algorithm();
return 0;
}