一、 策略(Strategy)模式
策略模式的用意是針對一組算法,將每一個(gè)算法封裝到具有共同接口的獨立的類(lèi)中,從而使得它們可以相互替換。策略模式使得算法可以在不影響到客戶(hù)端的情況下發(fā)生變化。
假設現在要設計一個(gè)販賣(mài)各類(lèi)書(shū)籍的電子商務(wù)網(wǎng)站的購物車(chē)(ShoppingCat)系統。一個(gè)最簡(jiǎn)單的情況就是把所有貨品的單價(jià)乘上數量,但是實(shí)際情況肯定比這要復雜。比如,本網(wǎng)站可能對所有的教材類(lèi)圖書(shū)實(shí)行每本一元的折扣;對連環(huán)畫(huà)類(lèi)圖書(shū)提供每本7%的促銷(xiāo)折扣,而對非教材類(lèi)的計算機圖書(shū)有3%的折扣;對其余的圖書(shū)沒(méi)有折扣。由于有這樣復雜的折扣算法,使得價(jià)格計算問(wèn)題需要系統地解決。
使用策略模式可以把行為和環(huán)境分割開(kāi)來(lái)。環(huán)境類(lèi)負責維持和查詢(xún)行為類(lèi),各種算法則在具體策略類(lèi)(ConcreteStrategy)中提供。由于算法和環(huán)境獨立開(kāi)來(lái),算法的增減、修改都不會(huì )影響環(huán)境和客戶(hù)端。當出現新的促銷(xiāo)折扣或現有的折扣政策出現變化時(shí),只需要實(shí)現新的策略類(lèi),并在客戶(hù)端登記即可。策略模式相當于"可插入式(Pluggable)的算法"。
二、 策略模式的結構
策略模式是對算法的包裝,是把使用算法的責任和算法本身分割開(kāi),委派給不同的對象管理。策略模式通常把一個(gè)系列的算法包裝到一系列的策略類(lèi)里面,作為一個(gè)抽象策略類(lèi)的子類(lèi)。用一句話(huà)來(lái)說(shuō),就是:"準備一組算法,并將每一個(gè)算法封裝起來(lái),使得它們可以互換。"
策略又稱(chēng)做政策(Policy)模式【GOF95】。下面是一個(gè)示意性的策略模式結構圖:
這個(gè)模式涉及到三個(gè)角色:
- 環(huán)境(Context)角色:持有一個(gè)Strategy類(lèi)的引用。
- 抽象策略(Strategy)角色:這是一個(gè)抽象角色,通常由一個(gè)接口或抽象類(lèi)實(shí)現。此角色給出所有的具體策略類(lèi)所需的接口。
- 具體策略(ConcreteStrategy)角色:包裝了相關(guān)的算法或行為。
三、 示意性源代碼
// Strategy pattern -- Structural example
using System;
// "Strategy"
abstract class Strategy
{
// Methods
abstract public void AlgorithmInterface();
}
// "ConcreteStrategyA"
class ConcreteStrategyA : Strategy
{
// Methods
override public void AlgorithmInterface()
{
Console.WriteLine("Called ConcreteStrategyA.AlgorithmInterface()");
}
}
// "ConcreteStrategyB"
class ConcreteStrategyB : Strategy
{
// Methods
override public void AlgorithmInterface()
{
Console.WriteLine("Called ConcreteStrategyB.AlgorithmInterface()");
}
}
// "ConcreteStrategyC"
class ConcreteStrategyC : Strategy
{
// Methods
override public void AlgorithmInterface()
{
Console.WriteLine("Called ConcreteStrategyC.AlgorithmInterface()");
}
}
// "Context"
class Context
{
// Fields
Strategy strategy;
// Constructors
public Context( Strategy strategy )
{
this.strategy = strategy;
}
// Methods
public void ContextInterface()
{
strategy.AlgorithmInterface();
}
}
/**////
/// Client test
/// public class Client
{
public static void Main( string[] args )
{
// Three contexts following different strategies
Context c = new Context( new ConcreteStrategyA() );
c.ContextInterface();
Context d = new Context( new ConcreteStrategyB() );
d.ContextInterface();
Context e = new Context( new ConcreteStrategyC() );
e.ContextInterface();
}
}
四、 何時(shí)使用何種具體策略角色
在學(xué)習策略模式時(shí),學(xué)員常問(wèn)的一個(gè)問(wèn)題是:為什么不能從策略模式中看出哪一個(gè)具體策略適用于哪一種情況呢?
答案非常簡(jiǎn)單,策略模式并不負責做這個(gè)決定。換言之,應當由客戶(hù)端自己決定在什么情況下使用什么具體策略角色。策略模式僅僅封裝算法,提供新算法插入到已有系統中,以及老算法從系統中"退休"的方便,策略模式并不決定在何時(shí)使用何種算法。
五、 一個(gè)實(shí)際應用策略模式的例子
下面的例子利用策略模式在排序對象中封裝了不同的排序算法,這樣以便允許客戶(hù)端動(dòng)態(tài)的替換排序策略(包括Quicksort、Shellsort和Mergesort)。
// Strategy pattern -- Real World example
using System;
using System.Collections;
// "Strategy"
abstract class SortStrategy
{
// Methods
abstract public void Sort( ArrayList list );
}
// "ConcreteStrategy"
class QuickSort : SortStrategy
{
// Methods
public override void Sort(ArrayList list )
{
list.Sort(); // Default is Quicksort
Console.WriteLine("QuickSorted list ");
}
}
// "ConcreteStrategy"
class ShellSort : SortStrategy
{
// Methods
public override void Sort(ArrayList list )
{
//list.ShellSort();
Console.WriteLine("ShellSorted list ");
}
}
// "ConcreteStrategy"
class MergeSort : SortStrategy
{
// Methods
public override void Sort( ArrayList list )
{
//list.MergeSort();
Console.WriteLine("MergeSorted list ");
}
}
// "Context"
class SortedList
{
// Fields
private ArrayList list = new ArrayList();
private SortStrategy sortstrategy;
// Constructors
public void SetSortStrategy( SortStrategy sortstrategy )
{
this.sortstrategy = sortstrategy;
}
// Methods
public void Sort()
{
sortstrategy.Sort( list );
}
public void Add( string name )
{
list.Add( name );
}
public void Display()
{
foreach( string name in list )
Console.WriteLine( " " + name );
}
}
/**////
/// StrategyApp test
/// public class StrategyApp
{
public static void Main( string[] args )
{
// Two contexts following different strategies
SortedList studentRecords = new SortedList( );
studentRecords.Add( "Samual" );
studentRecords.Add( "Jimmy" );
studentRecords.Add( "Sandra" );
studentRecords.Add( "Anna" );
studentRecords.Add( "Vivek" );
studentRecords.SetSortStrategy( new QuickSort() );
studentRecords.Sort();
studentRecords.Display();
}
}
六、 在什么情況下應當使用策略模式
在下面的情況下應當考慮使用策略模式:
1. 如果在一個(gè)系統里面有許多類(lèi),它們之間的區別僅在于它們的行為,那么使用策略模式可以動(dòng)態(tài)地讓一個(gè)對象在許多行為中選擇一種行為。
2. 一個(gè)系統需要動(dòng)態(tài)地在幾種算法中選擇一種。那么這些算法可以包裝到一個(gè)個(gè)的具體算法類(lèi)里面,而這些具體算法類(lèi)都是一個(gè)抽象算法類(lèi)的子類(lèi)。換言之,這些具體算法類(lèi)均有統一的接口,由于多態(tài)性原則,客戶(hù)端可以選擇使用任何一個(gè)具體算法類(lèi),并只持有一個(gè)數據類(lèi)型是抽象算法類(lèi)的對象。
3. 一個(gè)系統的算法使用的數據不可以讓客戶(hù)端知道。策略模式可以避免讓客戶(hù)端涉及到不必要接觸到的復雜的和只與算法有關(guān)的數據。
4. 如果一個(gè)對象有很多的行為,如果不用恰當的模式,這些行為就只好使用多重的條件選擇語(yǔ)句來(lái)實(shí)現。此時(shí),使用策略模式,把這些行為轉移到相應的具體策略類(lèi)里面,就可以避免使用難以維護的多重條件選擇語(yǔ)句,并體現面向對象設計的概念。
七、 策略模式的優(yōu)點(diǎn)和缺點(diǎn)
策略模式有很多優(yōu)點(diǎn)和缺點(diǎn)。它的優(yōu)點(diǎn)有:
1. 策略模式提供了管理相關(guān)的算法族的辦法。策略類(lèi)的等級結構定義了一個(gè)算法或行為族。恰當使用繼承可以把公共的代碼移到父類(lèi)里面,從而避免重復的代碼。
2. 策略模式提供了可以替換繼承關(guān)系的辦法。繼承可以處理多種算法或行為。如果不是用策略模式,那么使用算法或行為的環(huán)境類(lèi)就可能會(huì )有一些子類(lèi),每一個(gè)子類(lèi)提供一個(gè)不同的算法或行為。但是,這樣一來(lái)算法或行為的使用者就和算法或行為本身混在一起。決定使用哪一種算法或采取哪一種行為的邏輯就和算法或行為的邏輯混合在一起,從而不可能再獨立演化。繼承使得動(dòng)態(tài)改變算法或行為變得不可能。
3. 使用策略模式可以避免使用多重條件轉移語(yǔ)句。多重轉移語(yǔ)句不易維護,它把采取哪一種算法或采取哪一種行為的邏輯與算法或行為的邏輯混合在一起,統統列在一個(gè)多重轉移語(yǔ)句里面,比使用繼承的辦法還要原始和落后。
策略模式的缺點(diǎn)有:
1. 客戶(hù)端必須知道所有的策略類(lèi),并自行決定使用哪一個(gè)策略類(lèi)。這就意味著(zhù)客戶(hù)端必須理解這些算法的區別,以便適時(shí)選擇恰當的算法類(lèi)。換言之,策略模式只適用于客戶(hù)端知道所有的算法或行為的情況。
2. 策略模式造成很多的策略類(lèi)。有時(shí)候可以通過(guò)把依賴(lài)于環(huán)境的狀態(tài)保存到客戶(hù)端里面,而將策略類(lèi)設計成可共享的,這樣策略類(lèi)實(shí)例可以被不同客戶(hù)端使用。換言之,可以使用享元模式來(lái)減少對象的數量。
八、 其它
策略模式與很多其它的模式都有著(zhù)廣泛的聯(lián)系。Strategy很容易和Bridge模式相混淆。雖然它們結構很相似,但它們卻是為解決不同的問(wèn)題而設計的。Strategy模式注重于算法的封裝,而B(niǎo)ridge模式注重于分離抽象和實(shí)現,為一個(gè)抽象體系提供不同的實(shí)現。Bridge模式與Strategy模式都很好的體現了"Favor composite over inheritance"的觀(guān)點(diǎn)。
推薦大家讀一讀《IoC 容器和Dependency Injection 模式》,作者M(jìn)artin Fowler。網(wǎng)上可以找到中文版的PDF文件。為策略模式的實(shí)施提供了一個(gè)非常好的方案。