示例代码来自DoFactory。
概述
策略模式通过将实现相同功能的不同方法封装起来,使调用者可以方便的在需要的方法之间做出选择。
意图
定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
UML
图1 策略模式的UML图
参与者
这个模式涉及的类或对象:
-
Strategy
-
声明一个访问所有支持的算法的公共接口。程序上下文使用这个接口调用实现于一个ConcreteStrategy中的算法。
-
ConcreteStrategy
-
实现实现了Strategy中接口的算法
-
Context
-
使用一个ConcreteStrategy对象来配置
-
维护一个到Strategy对象的引用
-
可以定义一个接口,是Strategy对象可以访问其数据。
适用性
策略模式被广泛使用。其意图是为一个特定的操作封装其中可替换的策略。策略模式是一种即插即用的模式。客户端调用一个特定接口上的方法,正是如此,被调用方可以替换为其它任何实现了相同接口策略类。策略模式在许多不同的场景中都很有用。一个例子是信用卡处理。在一个电子商城中,如果客户希望使用PayPal而不是2Checkout支付,电子商城应用程序可以很容易的将处理2Checkout支付的策略类换成PayPal的策略类 。
如果策略接口只有一个单一方法 ,你可以将其简化为使用一个委托而不是接口来实现。想想看,委托是策略模式的一个特例。因此,你可以把.NET的事件模型(使用委托作为事件处理函数)也看作是建立在策略模式之上。
下面是几个策略模式的具体使用的场景:
-
许多相关的类仅仅是行为有异。"策略"提供了一种用多个行为中的一个行为来配置一个类的方法。
-
需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间/时间权衡的算法。当这些变体实现为一个算法的类层次时,可以使用策略模式。
-
算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
-
一个类定义了多种行为, 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的 Strategy 类中以代替这些条件语句。
DoFactory GoF代码
标准代码展示了使用策略模式将功能以对象方式来封装。这使客户端可以动态改变算法策略。
// Strategy pattern // Structural example using System; namespace DoFactory.GangOfFour.Strategy.Structural { // MainApp test application class MainApp { static void Main() { Context context; // Three contexts following different strategies context = new Context(new ConcreteStrategyA()); context.ContextInterface(); context = new Context(new ConcreteStrategyB()); context.ContextInterface(); context = new Context(new ConcreteStrategyC()); context.ContextInterface(); // Wait for user Console.ReadKey(); } } // "Strategy" abstract class Strategy { public abstract void AlgorithmInterface(); } // "ConcreteStrategy" class ConcreteStrategyA : Strategy { public override void AlgorithmInterface() { Console.WriteLine("Called ConcreteStrategyA.AlgorithmInterface()"); } } // "ConcreteStrategy" class ConcreteStrategyB : Strategy { public override void AlgorithmInterface() { Console.WriteLine("Called ConcreteStrategyB.AlgorithmInterface()"); } } // "ConcreteStrategy" class ConcreteStrategyC : Strategy { public override void AlgorithmInterface() { Console.WriteLine("Called ConcreteStrategyC.AlgorithmInterface()"); } } // "Context" class Context { private Strategy _strategy; // Constructor public Context(Strategy strategy) { this._strategy = strategy; } public void ContextInterface() { _strategy.AlgorithmInterface(); } } }
实际应用展示了使用策略模式将排序算法以排序对象方式来封装。这使客户端可以动态改变包括冒泡、希尔排序和合并排序在内的排序策略。
例子中涉及到的类与职责链模式中标准的类对应关系如下:
-
Strategy – SortStrategy
-
ConcreteStrategy – QuickSort, ShellSort, MergeSort
-
Context - SortedList
// Strategy pattern // Real World example using System; using System.Collections.Generic; namespace DoFactory.GangOfFour.Strategy.RealWorld { // MainApp test application class MainApp { static void Main() { // Two contexts following different strategies SortedList studentRecords = new SortedList(); studentRecords.Add("Samual"); studentRecords.Add("Jimmy"); studentRecords.Add("Sandra"); studentRecords.Add("Vivek"); studentRecords.Add("Anna"); studentRecords.SetSortStrategy(new QuickSort()); studentRecords.Sort(); studentRecords.SetSortStrategy(new ShellSort()); studentRecords.Sort(); studentRecords.SetSortStrategy(new MergeSort()); studentRecords.Sort(); // Wait for user Console.ReadKey(); } } // "Strategy" abstract class SortStrategy { public abstract void Sort(List<string> list); } // "ConcreteStrategy" class QuickSort : SortStrategy { public override void Sort(List<string> list) { list.Sort(); // Default is Quicksort Console.WriteLine("QuickSorted list "); } } // "ConcreteStrategy" class ShellSort : SortStrategy { public override void Sort(List<string> list) { //list.ShellSort(); not-implemented Console.WriteLine("ShellSorted list "); } } // "ConcreteStrategy" class MergeSort : SortStrategy { public override void Sort(List<string> list) { //list.MergeSort(); not-implemented Console.WriteLine("MergeSorted list "); } } // "Context" class SortedList { private List<string> _list = new List<string>(); private SortStrategy _sortstrategy; public void SetSortStrategy(SortStrategy sortstrategy) { this._sortstrategy = sortstrategy; } public void Add(string name) { _list.Add(name); } public void Sort() { _sortstrategy.Sort(_list); // Iterate over list and display results foreach (string name in _list) { Console.WriteLine(" " + name); } Console.WriteLine(); } } }
.NET优化版代码实现了与实际应用示例相同的功能但更多的采用了现代化的.NET内置特性。在本例中ISortStrategy接口替代了SortStrategy抽象类。方法SetStrategy被实现为一个.NET属性。学生的集合使用一个类型安全的泛型列表来实现。Student类使用了.NET 3.0的自动属性和对象初始化器。
// Strategy pattern // .NET Optimized example using System; using System.Collections.Generic; namespace DoFactory.GangOfFour.Strategy.NETOptimized { class MainApp { static void Main() { // Two contexts following different strategies var studentRecords = new SortedList() { new Student{ Name = "Samual", Ssn = "154-33-2009" }, new Student{ Name = "Jimmy", Ssn = "487-43-1665" }, new Student{ Name = "Sandra", Ssn = "655-00-2944" }, new Student{ Name = "Vivek", Ssn = "133-98-8399" }, new Student{ Name = "Anna", Ssn = "760-94-9844" }, }; studentRecords.SortStrategy = new QuickSort(); studentRecords.SortStudents(); studentRecords.SortStrategy = new ShellSort(); studentRecords.SortStudents(); studentRecords.SortStrategy = new MergeSort(); studentRecords.SortStudents(); // Wait for user Console.ReadKey(); } } // "Strategy" interface ISortStrategy { void Sort(List<Student> list); } // "ConcreteStrategy" class QuickSort : ISortStrategy { public void Sort(List<Student> list) { // Call overloaded Sort Sort(list, 0, list.Count - 1); Console.WriteLine("QuickSorted list "); } // Recursively sort private void Sort(List<Student> list, int left, int right) { int lhold = left; int rhold = right; // Use a random pivot var random = new Random(); int pivot = random.Next(left, right); Swap(list, pivot, left); pivot = left; left++; while (right >= left) { int compareleft = list[left].Name.CompareTo(list[pivot].Name); int compareright = list[right].Name.CompareTo(list[pivot].Name); if ((compareleft >= 0) && (compareright < 0)) { Swap(list, left, right); } else { if (compareleft >= 0) { right--; } else { if (compareright < 0) { left++; } else { right--; left++; } } } } Swap(list, pivot, right); pivot = right; if (pivot > lhold) Sort(list, lhold, pivot); if (rhold > pivot + 1) Sort(list, pivot + 1, rhold); } // Swap helper function private void Swap(List<Student> list, int left, int right) { var temp = list[right]; list[right] = list[left]; list[left] = temp; } } // "ConcreteStrategy" class ShellSort : ISortStrategy { public void Sort(List<Student> list) { // ShellSort(); not-implemented Console.WriteLine("ShellSorted list "); } } // "ConcreteStrategy" class MergeSort : ISortStrategy { public void Sort(List<Student> list) { // MergeSort(); not-implemented Console.WriteLine("MergeSorted list "); } } // "Context" class SortedList : List<Student> { // Sets sort strategy public ISortStrategy SortStrategy { get; set; } // Perform sort public void SortStudents() { SortStrategy.Sort(this); // Display sort results foreach (var student in this) { Console.WriteLine(" " + student.Name); } Console.WriteLine(); } } /// <summary> /// Represents a student /// </summary> class Student { // Gets or sets student name public string Name { get; set; } // Gets or sets student social security number public string Ssn { get; set; } } }
来自《深入浅出设计模式》的例子
这个例子中使用策略模式,将不同种类鸭子的同一个行为的不同实现封装起来,使一个行为的不同实现可以灵活的互相替换,并且不影响鸭子(调用者)对象本身。
using System; namespace DoFactory.HeadFirst.Strategy { public class MiniDuckSimulator { static void Main(string[] args) { Duck mallard = new MallardDuck(); mallard.Display(); mallard.PerformQuack(); mallard.PerformFly(); Console.WriteLine(""); Duck model = new ModelDuck(); model.Display(); model.PerformFly(); model.FlyBehavior = new FlyRocketPowered(); model.PerformFly(); // Wait for user input Console.ReadKey(); } } #region Duck public abstract class Duck { public IFlyBehavior FlyBehavior { get; set; } public IQuackBehavior QuackBehavior { get; set; } public abstract void Display(); public void PerformFly() { FlyBehavior.Fly(); } public void PerformQuack() { QuackBehavior.Quack(); } public void Swim() { Console.WriteLine("All ducks float, even decoys!"); } } public class MallardDuck : Duck { public MallardDuck() { QuackBehavior = new LoudQuack(); FlyBehavior = new FlyWithWings(); } override public void Display() { Console.WriteLine("I'm a real Mallard duck"); } } public class ModelDuck : Duck { public ModelDuck() { QuackBehavior = new LoudQuack(); FlyBehavior = new FlyNoWay(); } override public void Display() { Console.WriteLine("I'm a model duck"); } } #endregion #region FlyBehavior public interface IFlyBehavior { void Fly(); } public class FlyWithWings : IFlyBehavior { public void Fly() { Console.WriteLine("I'm flying!!"); } } public class FlyNoWay : IFlyBehavior { public void Fly() { Console.WriteLine("I can't fly"); } } public class FlyRocketPowered : IFlyBehavior { public void Fly() { Console.WriteLine("I'm flying with a rocket!"); } } #endregion #region QuackBehavior public interface IQuackBehavior { void Quack(); } // Name it LoadQuack to avoid conflict with method name public class LoudQuack : IQuackBehavior { public void Quack() { Console.WriteLine("LoudQuack"); } } public class MuteQuack : IQuackBehavior { public void Quack() { Console.WriteLine("<< Silence >>"); } } public class Squeak : IQuackBehavior { public void Quack() { Console.WriteLine("Squeak"); } } #endregion }
来自《大话设计模式》的例子
这个例子中实现了一个商场收银系统,不同的打折促销规则被定义为一个个具体策略,这样可以方面它们被灵活的替换。值得注意的是这个例子的Context(CashContext)类的实现中使用了简单工厂模式,这样具体策略被封装在Context内部,不需要被Client了解,这样进一步解耦合。
using System; //现金收取父类 abstract class CashSuper { //抽象方法:收取现金,参数为原价,返回为当前价 public abstract double acceptCash(double money); } //正常收费,继承CashSuper class CashNormal : CashSuper { public override double acceptCash(double money) { return money; } } //返利收费,继承CashSuper class CashReturn : CashSuper { private double moneyCondition = 0.0d; private double moneyReturn = 0.0d; //初始化时必须要输入返利条件和返利值,比如满300返100,则moneyCondition为300,moneyReturn为100 public CashReturn(string moneyCondition, string moneyReturn) { this.moneyCondition = double.Parse(moneyCondition); this.moneyReturn = double.Parse(moneyReturn); } public override double acceptCash(double money) { double result = money; //若大于返利条件,则需要减去返利值 if (money >= moneyCondition) result = money - Math.Floor(money / moneyCondition) * moneyReturn; return result; } } //打折收费,继承CashSuper class CashRebate : CashSuper { private double moneyRebate = 1d; //初始化时,必需要输入折扣率,如八折,就是0.8 public CashRebate(string moneyRebate) { this.moneyRebate = double.Parse(moneyRebate); } public override double acceptCash(double money) { return money * moneyRebate; } } //现金收取工厂 class CashContext { CashSuper cs = null; //根据条件返回相应的对象 public CashContext(string type) { switch (type) { case "正常收费": CashNormal cs0 = new CashNormal(); cs = cs0; break; case "满300返100": CashReturn cr1 = new CashReturn("300", "100"); cs = cr1; break; case "打8折": CashRebate cr2 = new CashRebate("0.8"); cs = cr2; break; } } public double GetResult(double money) { return cs.acceptCash(money); } } static void Main(string[] args) { double total = 0.0d; if (args.Length < 3) { return; } string inputCostType = args[0]; string price = args[1]; string num = args[2]; //利用简单工厂模式根据用户输入,生成相应的对象 CashContext csuper = new CashContext(inputCostType); double totalPrices = 0d; //通过多态,可以得到收取费用的结果 totalPrices = csuper.GetResult(Convert.ToDouble(price) * Convert.ToDouble(num)); total = total + totalPrices; Console.WriteLine("单价:" + price + " 数量:" + num + " " + inputCostType + " 合计:" + totalPrices); }
.NET中的策略模式
.NET中一个策略模式的例子是包含几个重载的Sort方法 的ArrayList类。这些方法使用一个给定的实现IComparer接口的类对一个列表中的元素进行排序。IComparer接口包含一个Sort方法,该方法比较两个对象并返回一个值指示一个对象是大于、等于还是小于另一个。实现IComparer接口的类正是策略模式的实现。
效果及实现要点
策略模式的Strategy类层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能。
策略模式的优点是简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。
不同的行为堆砌在一个类中时,就很难避免使用条件语句来选择合适的行为。将这些行为封装在一个个独立的Strategy类中,可以在使用这些行为的类中消除条件语句。
策略模式就是用来封装算法的,但在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同业务的业务规则,就可以考虑使用策略模式处理这种变化的可能性。
使用策略模式时,常常将反射技术结合在内,通过反射来在运行时加载具体策略的类,大大提高程序的灵活性。
总结
策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。