意圖
定義一系列的算法,把它們一個一個封裝起來,並且使它們可相互替換。本模式使得算法可以獨立於它的客戶而變化。
場景
在開發程序的時候,我們經常會根據環境不同采取不同的算法對對象進行處理。比如,在一個新聞列表頁面需要顯示所有新聞,而在一個新聞搜索頁面需要根據搜索關鍵詞顯示匹配的新聞。如果在新聞類內部有一個ShowData方法的話,那麼我們可能會傳入一個searchWord的參數,並且在方法內判斷如果參數為空則顯示所有新聞,如果參數不為空則進行搜索。如果還有分頁的需求,那麼還可能在方法內判斷是否分頁等等。
這樣做有幾個不好的地方,一是ShowData方法太復雜了,代碼不容易維護並且可能會降低性能,二是如果還有其它需求的話勢必就需要修改ShowData方法,三是不利於重用一些算法的共同部分。由此引入策略模式,把算法進行封裝,使之可以靈活擴展和替換。
示例代碼
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
namespace StrategyExample
{
class Program
{
static void Main(string[] args)
{
Data data = new Data();
data.Add("aaa");
data.Add("bbb");
data.Add("ccc");
data.Add("abc");
data.Show();
data.SetShowDataStrategy(new SearchData("a"));
data.Show();
data.SetShowDataStrategy(new ShowPagedData(2,1));
data.Show();
}
}
abstract class ShowDataStrategy
{
abstract public void ShowData(IList data);
}
class ShowAllData : ShowDataStrategy
{
public override void ShowData(IList data)
{
for (int i = 0; i < data.Count; i++)
{
Console.WriteLine(data[i]);
}
}
}
class ShowPagedData : ShowDataStrategy
{
private int pageSize;
private int pageIndex;
public ShowPagedData(int pageSize, int pageIndex)
{
this.pageSize = pageSize;
this.pageIndex = pageIndex;
}
public override void ShowData(IList data)
{
for (int i = pageSize * pageIndex; i < pageSize * (pageIndex + 1); i++)
{
Console.WriteLine(data[i]);
}
}
}
class SearchData : ShowDataStrategy
{
private string searchWord;
public string SearchWord
{
get { return searchWord; }
set { searchWord = value; }
}
public SearchData(string searchWord)
{
this.searchWord = searchWord;
}
public override void ShowData(IList data)
{
for (int i = 0; i < data.Count; i++)
{
if (data[i].ToString().Contains(searchWord))
Console.WriteLine(data[i]);
}
}
}
class Data
{
private ShowDataStrategy strategy;
private IList data = new ArrayList();
public void SetShowDataStrategy(ShowDataStrategy strategy)
{
this.strategy = strategy;
}
public void Show()
{
if (strategy == null )
strategy = new ShowAllData();
Console.WriteLine(strategy.GetType().ToString());
strategy.ShowData(data);
}
public void Add(string name)
{
data.Add(name);
}
}
}
代碼執行結果如下圖:
代碼說明
l Data類就是環境或者說上下文角色,持有對策略角色的引用。在這裡,我們通過一個方法來設置環境使用的策略,你也可以根據需求在構造方法中傳入具體策略對象。
l ShowDataStrategy抽象類就是抽象策略角色,它定義了策略共有的接口。
l ShowAllData、ShowPagedData以及SearchData類都是具體策略角色,它們實現真正的算法或行為。
l 客戶端在調用的時候才決定去使用哪種策略模式。
l 可以看到,由於顯示數據由各個具體策略類來實現,使得環境角色的復雜度降低了很多。並且如果以後還需要增加新的顯示數據方式的話只需要增加新的具體策略類(實現抽象策略接口)就可以了,環境類的代碼不需要做改動。對於各具體策略實現過程中可復用的部分也可以放在抽象策略類中實現。
何時采用
l 從代碼角度來說, 如果一個類有多種行為,並且在類內部通過條件語句來實現不同的行為的時候可以把這些行為單獨封裝為策略類。
l 從應用角度來說,如果系統需要選擇多種算法中的一種並且希望通過統一的接口來獲取算法的輸出的話可以考慮策略模式。
實現要點
l 在環境角色中擁有策略角色的實例。
l 如果策略角色需要使用環境中的數據,一般可以讓環境把數據傳給所有策略角色,或者可以讓環境把自身傳給策略角色,前者會帶來不必要的通訊開銷,後者會使環境和策略角色發生緊密耦合。根據需要選擇合適的方式。
l 環境角色可以在客戶端沒有提供策略角色的時候可以實現模式的策略。
注意事項
l 策略模式的缺點是客戶端需要了解具體的策略,因此僅當客戶端能做出這樣選擇的時候才去使用策略模式。
l 過多的策略對象可能會增加系統負擔,可以考慮把各種策略角色實現為無狀態對象的享元,需要保存的額外狀態由環境角色進行統一管理和處理。