面对算法的经常变动,我们就要学习使用策略模式了。
策略模式(Strategy):它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
它包括三个组成部分:
1.抽象策略角色:策略类,通常由一个接口或者抽象类实现。
2.具体策略角色:包装了相关的算法和行为。
3.环境角色:持有一个策略类的引用,最终给客户端调用。
这里面的封装变化点是我们面向对象的一种很重要的思维方式。下面是策略模式(strategy)的结构图和基本代码:
结构图:
具体代码:
1.抽象策略类:
//抽象算法类
abstract class Strategy
{
//算法方法
public abstract voidAlgorithmInterface();
}
2.具体策略类:
//具体算法类A
class ConcreteStrategyA : Strategy
{
public override voidAlgorithmInterface() //继承抽象类的方法
{
Console.WriteLine ("算法A实现");
}
}
3.环境角色Context
//Context,用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用
class Context
{
Strategy strategy;
//初始化时,传入具体的策略对象
public Context(Strategy strategy)
{
this.strategy = strategy;
}
//根据具体的策略方法,调用其算法的方法
public void ContextInterface()
{
strategy.AlgorithmInterface();
}
}
客户端代码:
static void Main(string[] args)
{
Context context;
//由于实例化不同的策略,所以最终在调用context.ContextInterface();时,所获得的结果就不尽相同
context = new Context(newConcreteStrategyA());
context.ContextInterface();
Console.Read();
}
通过以上代码,我们可以发现抽象类只是封装了相关的算法,具体实现时的判断过程还是要从客户端负责的。
例如商场收费系统的客户端代码是:
private void btnOk_Click(object sender,EventArgs e)
{
CashContext cc = null;
switch(cboType.SelectedItem.ToString())
{
case "正常收费":
cc = new CashContext(new cashnormal());
break;
... }
//通过对Context的GetResult方法的调用,可以得到收取费用的结果,让具体的算法与客户进行隔离
totalunit = cc.GetResult(Convert.ToDouble (txtUnitPrice .Text )*Convert .ToDouble (txtNum .Text ));
}
这时候大家想到刚刚学习的简单工厂模式了吗?没错,用它是可以将判断过程从客户端转移走的,例如下面的代码,判断过程是在工厂类中,不再需要客户端来负责了:
//现金收费工厂类
class CashTypeFactory
{
public static CashSuper CreateCashAccept(string Type)
{
CashSuper cs=null ;
switch (Type)
{
case"正常收费":
cs = new cashnormal();
break;
....
}
return cs ;
}
}
所以,为了弥补策略模式的不足,就要结合简单工厂模式了。那么策略与简单工厂如何结合呢?其实非常简单,只需要修改一下CashContext类和部分客户端代码即可。例如:
//改写后的CashContext类-策略模式和简单工厂结合
class CashContext
{
private CashSupercs = null; //声明一个CashSuper对象
public CashContext(stringtype) //参数不是具体的收费策略对象,而是一个字符串,表示收费类型
{
switch (type)
{
//将实例化具体策略的过程由客户端转移到Context类中。简单工厂的应用
case"正常收费":
cashnormal cn = newcashnormal();
cs = cn;
break;
.... }
}
public double GetResult(doublemoney)
{
return cs.accptCash(money);
}
}
客户端代码是:
private void btnOk_Click(object sender, EventArgs e)
{
//根据下拉框的选择,将相应的算法类型字符串传入CashContext的对象中
CashContext csuper = new CashContext(cboType.SelectedItem.ToString());
totalunit = csuper.GetResult(Convert.ToDouble(txtUnitPrice.Text) * Convert.ToDouble(txtNum.Text));
}
或者可以更简单的比较一下简单工厂模式和策略与简单工厂结合的差异:
//简单工厂模式的用法
CashSuper csuper =CashTypeFactory.CreateCashAccept(cboType.SelectedItem.ToString ());
… = csuper.GetResult(…) ;
//策略模式与简单工厂结合的用法
CashContext csuper =new CashContext(cboType.SelectedItem.ToString());
… = csuper.GetResult(…) ;
可以看出,简单工厂模式需要让客户端认识两个类,CashSuper和CashFactory,而策略模式与简单工厂结合的用法,客户端就只需要认识一个类CashContext就可以了,耦合更加降低。
回过头来再看策略模式,它是一种定义一系列算法的方法,从概念上看,所有算法完成的都是相同工作,只是实现不同,它可以用相同的方法调用所有的算法,减少了各种算法类与使用算法类之间的耦合。
它的优点是:
1、 提供了一种替代继承的方法,而且既保持了继承的优点(代码重用)还比继承更灵活(算法独立,可以任意扩展)。
2、 避免程序中使用多重条件转移语句,使系统更灵活,并易于扩展。
3、 遵守大部分GRASP原则和常用设计原则,高内聚、低偶合。
也有缺点:
1、 因为每个具体策略类都会产生一个新类,所以会增加系统需要维护的类的数量。解决方案:工厂方法。