策略模式是咱们工做中比较经常使用的一个设计模式,可是初次理解起来可能会有点困难,所以咱们仍是先看一个例子,假设如今须要开发一个画图工具,画图工具中有钢笔,笔刷和油漆桶,其中,钢笔能够用于描边,但不能填充,笔刷既能够描边,也能够填充,油漆桶只能用于填充,但不能描边。git
看到这个需求,最容易想到的可能就是经过继承方式实现了,即钢笔,笔刷和油漆桶都继承自画图工具,而后都实现描边和填充功能,只不过钢笔的填充方法什么都不作,油漆桶的描边方法也什么都不作。(该部分在设计原则中有示例代码,是当时的一个遗留问题)github
可是仔细一想,好像哪里不对劲,由于钢笔和油漆桶部分方法不实现,很明显违背了里氏替换原则。并且,正常状况应该是画图工具备用钢笔,笔刷,油漆桶画图的能力,而不是钢笔,笔刷,油漆桶继承自画图工具。所以,咱们能够以下实现:算法
public class Graphics { public void Stroke(ToolEnum tool) { switch (tool) { case ToolEnum.Pen: Console.WriteLine($"用钢笔描边图形"); break; case ToolEnum.Brush: Console.WriteLine($"用笔刷描边图形"); break; case ToolEnum.Bucket: Console.WriteLine("油漆桶不能描边图形"); break; default: throw new NotSupportedException("不支持的画图工具"); } } public void Fill(ToolEnum tool) { switch (tool) { case ToolEnum.Pen: Console.WriteLine($"钢笔不能填充图形"); break; case ToolEnum.Brush: Console.WriteLine($"用笔刷填充图形"); break; case ToolEnum.Bucket: Console.WriteLine("用油漆桶填充图形"); break; default: throw new NotSupportedException("不支持的画图工具"); } } }
经过上面枚举的方式,咱们实现了让画图工具具有描边和填充的能力,可是这样的switch-case
(或者if-else
)给扩展和维护都带来了很大的麻烦,并且经过前面对其余设计模式的学习,我相信你们看到这样的代码,必定是不能接受的。起码应该将Pen
,Brush
,Bucket
定义成类而且继承自同一个基类,而后组合到Graphics
中来,而不是直接使用条件判断,由于用组合代替继承是咱们学习设计模式过程当中百试不爽的经验。可是,此次好像不怎么灵了,由于,一旦这样作,咱们就又回到了原点---钢笔和油漆桶不得不实现不须要的方法。事实上,用组合替代继承是没有错的,可是该怎么组合?组合谁呢?这是个问题。感受瞬间陷入了两难的局面,这时候策略模式就派上用场了,它不抽象钢笔、笔刷、油漆桶等具体事物,而是直接抽象描边和填充这两种能力,站在代码的角度上看,就是将方法封装成了对象(正应了那句一切皆对象),这正是策略模式最让人费解,但又最妙趣横生的地方。咱们直接看看用策略模式改进后的代码是怎样的,先抽象能力:数据库
public interface IStrokeStrategy { void Stroke(); } public class PenStrokeStrategy : IStrokeStrategy { public void Stroke() { Console.WriteLine($"用钢笔描边图形"); } } public class BrushStrokeStrategy : IStrokeStrategy { public void Stroke() { Console.WriteLine($"用笔刷描边图形"); } } public interface IFillStrategy { void Fill(); } public class BrushFillStrategy : IFillStrategy { public void Fill() { Console.WriteLine($"用笔刷填充图形"); } } public class BucketFillStrategy : IFillStrategy { public void Fill() { Console.WriteLine("用油漆桶填充图形"); } }
看到了吗?直接将Stroke
和Fill
两种能力定义成了接口,再经过不一样的子类去实现这种能力。而后再看看Graphics
类如何组合:设计模式
public class Graphics { private IStrokeStrategy _strokeStrategy; private IFillStrategy _fillStrategy; public Graphics(IStrokeStrategy strokeStrategy, IFillStrategy fillStrategy) { this._strokeStrategy = strokeStrategy; this._fillStrategy = fillStrategy; } public void Stroke() { this._strokeStrategy.Stroke(); } public void Fill() { this._fillStrategy.Fill(); } }
画图工具直接拥有了两种能力,可是跟钢笔、笔刷、油漆桶没有直接关系,也就是说,只要给画图工具一个填充的工具,就能够完成填充功能了,至于给的具体是笔刷仍是油漆桶,或者其余什么东西,画图工具并不关心。并且,Graphics
类中的条件判断语句也都去掉了,隔离了变化,整个类都变得稳定了。
以下就是经过策略模式实现的类图:
缓存
再来看一下定义,策略模式定义一系列算法,把他们一个个封装起来,而且使他们能够互相替换。该模式使得算法能够独立于使用它的客户程序而变化。框架
这里的一系列算法就是不一样的描边和填充方式了。不一样的描边方式能够相互替换,不一样的填充方式也能够相互替换,而且也能够方便的扩展更多的描边和填充方式子类。分布式
上面的例子中用到了两组算法,抽象简化以后就获得了以下策略模式的UML类图:
工具
IStrategy
的引用,负责和具体的策略实现交互;switch-case
、if-else
带来的难以维护的问题;第一个缺点没法避免,由于策略模式的一大优势就是算法能够相互替换,可是若是使用者连每一个算法表明的是什么意思,优缺点是什么都不知道,又如何替换呢?但第二个缺点却能够经过结合工厂模式,由工厂模式建立具体的策略子类来进行必定程度的缓解,至于具体该怎么实现,这就是工厂模式的知识了,你们能够自行回忆一下。学习
在业务场景中,商家促销活动就很是适合用到策略模式了,由于,商家促销打折可能存在会员折扣,节日折扣,生日折扣等等几十种方式,并且在不一样的条件下能够相互替换。
而非业务场景中,日志框架中咱们可能会使用log4Net
,NLog
,Serilog
等,而记录位置也多是控制台,文件,数据库等;系统中的缓存,能够用Redis
作分布式缓存,也能够用MemeryCache
作本地缓存等,这些场景也都很是适合使用策略模式。
总之,策略模式简约但不简单,学好它妙用无穷!