应用场景
当你在开发一个需求,需要根据不同的条件执行单独的操作,例如:一个购物积分系统需要根据顾客的城市实行符合当地情况的积分政策。可能很多开发者的想法是通过 if...else if...
的条件语句来实现政策的区分。一开始没什么问题,但当业务扩大,条件分支也越加越多,这时的代码就变得非常难以阅读,也难以维护。
这时可能有人会想到把城市定义成枚举,把条件语句改成 switch...case...
, 这一定程度上让代码整洁了一些。但代码修改起来还是很麻烦,改了一个条件分支可能所有分支都得测一遍,万一哪个分支忘写 break
了,很可能“牵一发而动全身”。
这时你可以试一下用策略模式去重新规划这坨代码,把每个城市的积分政策的具体实现封装成单独的策略实现类,这时原来每个 case:
下的n行代码可能就变成一行了(用于调用策略实现类)。
有人可能会问说代码是进一步整洁了不少,但这么多的条件分支看起来也很烦,能不能彻底消除掉条件语句呢?而且甲方的想法千变万化,可能今天是A城市A政策,B城市B政策,过几天就希望A和B城市都用C政策了呢?不还得去改已经写好的代码吗?有一个设计思想是把经常会变化的代码单独放在一个地方,不难看出无论城市和积分政策怎么变化,一个城市总会对应着某个积分政策,这是一种映射关系,所以我们可以通过 map<key-value>
来存放城市和政策的映射关系,我们只要在一个方法里提前注册好 城市枚举-积分政策实现类
的映射关系,原先的大段的条件语句就只需要两行代码了,一行在 map
中查找对应的积分政策实现类,一行用于调用该实现类。
这时你的代码已经基本完美了,需要修改积分政策的内容时,只需要去修改某个积分政策的实现类,需要给城市换一个政策就去注册的方法里修改,如果需要新增城市或者政策就加一个政策实现类,并在注册方法里注册一下就好。
如果你还有更高的追求,看那一行行的map插入语句很不爽,因为这里可能需要频繁修改或者新增映射关系,这项工作看起来机械而又重复,这时我们可以把这项乏味的工作交给程序或者其他人去做。我们可以利用编程语言的反射机制去做一些事,可以把每个映射关系封装成一个单独的实现类,Java可以通过自动注入把这些映射的实现类在程序启动时全部注入进去,C#可以通过一个 foreach
循环语句遍历某个程序集或者命名空间下的所有实现类,并把每个实现类的映射关系注册到 map
中。
这样,修改或新增映射关系时你就看不到原先冗长的插入语句了,只需要找到对应的映射关系实现类修改即可,甚至你可以通过配置的方式把这项工作交给你的老板或者运营,让他们在配置表里配上城市和积分政策的对应关系,你只需要写一段读取配置表内容并注册映射关系的代码即可。
从此,只有新增功能的时候需要新建一个实现类去实现,写过的代码不出 bug 可万年不动矣!
概述
说了那么多,简单进入下正题:策略模式通俗点说就是提供了几种方案(算法、策略),客户可以根据环境选择使用哪种方案。当不同的策略堆砌在一个类中时,就很难避免使用条件语句来选择合适的行为。将这些行为封装在一个个独立的类中,可以消除条件语句。
代码实现
Strategy类,抽象算法类,定义所有支持的算法的公共接口。
abstract class Strategy
{
//算法方法
public abstract void AlgorithmInterface();
}
ConcreteStrategy类,具体算法类。
class ConcreteStrategyA : Strategy
{
//算法方法
public override void AlgorithmInterface()
{
Console.WriteLine("算法A实现");
}
}
Context类,维护一个对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 = new Context(new ConcreteStrategyA());
context.ContextInterface();
}
策略模式与简单工厂结合
如果客户端不需要了解具体策略类,通过传入指定字符串就可以使用策略,那么可以加入简单工厂模式。
改造后的Context
//上下文
class Context
{
Strategy strategy = null;
public Context(string type)
{
switch(type)
{
case "A":
strategy = new ConcreteStrategyA();
break;
case "B":
strategy = new ConcreteStrategyB();
break;
}
}
//上下文接口,根据具体的策略对象,调用其算法方法
public void ContextInterface()
{
strategy.AlgorithmInterface();
}
}
因为在Context里用到了 switch
,也就是说如果增加一种策略,就要更改Context中的switch
代码,虽然比更改客户端的代码好,但是依然不够完美。还有更好的办法,那就是利用反射机制。