一、概要
我们构建程序的时候,会遇到这样的状况,对象有某个行为,但是在不同的场景中,使用策略模式,可以把它们一个个封装起来,并且使它们可相互替换,而起使得算法可独立于使用它的客户而变化。
二、生活举例
对于一个商店来讲,对不同的客户要报不同的价格,比如:
(1)对普通客户或者是新客户报的是全价
(2)对老客户(会员)报的价格,要给予一定的折扣
(3)对大客户(批发)报的价格,根据大客户购买量,给予一定的折扣
(4)根据不同的时间段,例如工作日和节假日等,可能价格仍然不同。
处理复杂的报价功能,就会用到策略模式。
三、实现思路
四、类图
五、注意点
1、策略模式的设计原则即把一个类中经常改变或者将来可能改变的部分提取出来,作为一个接口,然后在类中包含这个对象的实例,这样类的实例在运行时就可以随意调用实现了这个接口的类的行为。
2、策略模式,就是定义了不同的算法族,并且之间可以互相替换,此模式让算法的变化独立于使用算法的客户,即把会变化的内容取出并封装起来,以便以后可以轻易地改动或扩充部分,而不影响不需要变化的其他部分;这样便可以动态的改变对象的行为。
六、实例
1、说明:通过策略模式,简单工厂+策略模式,反射+策略模式三种方式实现需求,然后对比三种实现方式。
2、语言:C#
3、虚拟需求:《机房收费系统》计费方式:1、学生周一到周五免费 2、学生周六,周日1元/小时 3、临时用户任何时间都是1.5元/小时
代码1.0(策略模式标准实现):
using System;
namespace 策略模式
{
class Program
{
private string type;
public Program(string type)
{
this.type = type;
}
static void Main(string[] args)
{
Program pro = new Program("学生,周一到周五");
//Program pro = new Program("学生,周六周日");
//Program pro = new Program("临时用户,任意时间");
switch (pro.type)
{
case "学生,周一到周五":
Contest c1 = new Contest(new concreteStrategyA());
c1.ContestInterface();
break;
case "学生,周六周日":
Contest c2 = new Contest(new concreteStrategyB());
c2.ContestInterface();
break;
case "临时用户,任意时间":
Contest c3 = new Contest(new concreteStrategyC());
c3.ContestInterface();
break;
}
}
}
abstract class Strategy
{
//算法方法
public abstract void AlgorithmInterface();
}
//具体算法:学生周一到周五免费
class concreteStrategyA : Strategy
{
//算法A实现方法
public override void AlgorithmInterface()
{
Console.WriteLine("学生周一到周五免费");
}
}
//具体算法:学生周六,周日1元/小时
class concreteStrategyB : Strategy
{
//算法B实现方法
public override void AlgorithmInterface()
{
Console.WriteLine("学生周六,周日1元/小时");
}
}
//具体算法:临时用户任何时间都是1.5元/小时
class concreteStrategyC : Strategy
{
//算法C实现方法
public override void AlgorithmInterface()
{
Console.WriteLine("临时用户任何时间都是1.5元/小时");
}
}
class Contest
{
Strategy strategy;
public Contest(Strategy strategy)
{
this.strategy = strategy;
}
//上下文接口
public void ContestInterface()
{
strategy.AlgorithmInterface();
}
}
}
代码2.0(策略模式+简单工厂):
using System;
namespace 策略模式和简单工厂结合
{
class Program
{
static void Main(string[] args)
{
Contest c1 = new Contest("学生,周一到周五");
c1.ContestInterface();
Contest c2 = new Contest("学生,周六周日");
c2.ContestInterface();
Contest c3 = new Contest("临时用户,任意时间");
c3.ContestInterface();
}
}
abstract class Strategy
{
//算法方法
public abstract void AlgorithmInterface();
}
//具体算法:学生周一到周五免费
class concreteStrategyA : Strategy
{
//算法A实现方法
public override void AlgorithmInterface()
{
Console.WriteLine("学生周一到周五免费");
}
}
//具体算法:学生周六,周日1元/小时
class concreteStrategyB : Strategy
{
//算法B实现方法
public override void AlgorithmInterface()
{
Console.WriteLine("学生周六,周日1元/小时");
}
}
//具体算法:临时用户任何时间都是1.5元/小时
class concreteStrategyC : Strategy
{
//算法C实现方法
public override void AlgorithmInterface()
{
Console.WriteLine("临时用户任何时间都是1.5元/小时");
}
}
class Contest
{
Strategy strategy;
public Contest(string type)
{
switch (type)
{
case "学生,周一到周五":
concreteStrategyA a = new concreteStrategyA();
strategy = a;
break;
case "学生,周六周日":
concreteStrategyB b = new concreteStrategyB();
strategy = b;
break;
case "临时用户,任意时间":
concreteStrategyC c = new concreteStrategyC();
strategy = c;
break;
}
}
//上下文接口
public void ContestInterface()
{
strategy.AlgorithmInterface();
}
}
}
代码3.0(策略模式+反射):
using System;
using System.Reflection;
namespace 策略模式和反射结合
{
class Program
{
static void Main(string[] args)
{
Contest c1= new Contest("concreteStrategyA");
c1.ContestInterface();
Contest c2 = new Contest("concreteStrategyB");
c2.ContestInterface();
Contest c3 = new Contest("concreteStrategyC");
c3.ContestInterface();
}
}
abstract class Strategy
{
//算法方法
public abstract void AlgorithmInterface();
}
//具体算法:学生周一到周五免费
class concreteStrategyA : Strategy
{
//算法A实现方法
public override void AlgorithmInterface()
{
Console.WriteLine("学生周一到周五免费");
}
}
//具体算法:学生周六,周日1元/小时
class concreteStrategyB : Strategy
{
//算法B实现方法
public override void AlgorithmInterface()
{
Console.WriteLine("学生周六,周日1元/小时");
}
}
//具体算法:临时用户任何时间都是1.5元/小时
class concreteStrategyC : Strategy
{
//算法C实现方法
public override void AlgorithmInterface()
{
Console.WriteLine("临时用户任何时间都是1.5元/小时");
}
}
class Contest
{
private string type;
Strategy strategy;
public Contest(string Type)
{
this.type = Type;
//利用反射技术生成类的实例
strategy = (Strategy)Assembly.Load("策略和反射").CreateInstance("策略模式和反射结合." + this.type);
}
//上下文接口
public void ContestInterface()
{
strategy.AlgorithmInterface();
}
}
}
三种实现方式的比较:
发现代码1.0和代码2.0的主要区别,前者把Switch分支判断放在了客户端,后者利用工厂模式把Switch语句移到了contest中。
显然代码2.0隔离了客户端与具体的算法类,代码有一定优势。
但是,如果我们需要增加一种算法,比如‘教职工免费’,无论代码1.0还是代码2.0,都必须要更改switch代码,这不符合我们的开闭原则。面对同样的需求,当然是改动越小越好。
我们看到代码3.0中用反射技术屏蔽掉了switch语句。这样我们再增加新算法就不必修改context类。从这个角度上说,所有用简单工厂的地方,都可以考虑用反射技术去除选择语句,解除分支语句判断带来的耦合。
七、何时选用策略模式
1、出现有许多相关的类,仅仅是行为有差别的情况,可以使用策略模式来使用多个行为中的一个来配置一个类的方法,实现算法动态切换。
2、出现同一个算法,有很多不同的实现的情况,可以使用策略模式来把这些“不同的实现”实现成为一个算法的类层次。
3、需要封装算法中,与算法相关的数据的情况,可以使用策略模式来避免暴露这些跟算法相关的数据结构。
4、出现抽象一个定义了很多行为的类,并且是通过多个if-else语句来选择这些行为的情况,可以使用策略模式来代替这些条件语句。
综上,策略模式就是用来封装算法的,但在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要再不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。
八、总结
策略模式属于对象行为型模式,主要针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。通常,策略模式适用于当一个应用程序需要实现一种特定的服务或者功能,而且该程序有多种实现方式时使用。