策略模式的要点是封装一组算法,每个算法为独立的类,可以相互替代,因为它们有相似的行为。下面看一个具体的例子:
这是一个彩票网站,采用策略模式的真实案例。我们最终要计算不同彩种,不同方案,不同玩法的中奖率。3码:所有的3位数字,每位数字不能重复(022就不行),且按从小到大顺序排列。范围在012-789之间,4码:4位数字,推而广之,所谓几码,就是几位数字。因为时时彩是五位数字,所以有个十百千万。
2期方案,就是把最近100期,按2期划分为一组,(100,99),(98,97)等等,总共有50组。同理,3期方案,就是把最近100期,每3期划分为一组。
那如何计算中奖率呢?以上图为模型,我们现在选择了新疆时时彩的最近100期,2期方案,考察个位和十位,计算3码、4码、5码、6码的中奖率。比如说3码中的 0,1,2的中奖率的计算方法,如下图所示:
这是从1期到10期的中奖号码,末尾为12的有3组,总共5组,那么中奖率为 3/5 。同理,如果100期,里面中奖的组有N个,总共有50组,所以中奖率为 N/50。
我们先定义一个抽象的玩法类:
1 public abstract class Play 2 { 3 /// <summary> 4 /// 中奖号码 5 /// </summary> 6 public List<string> PrizeNums { set; get; } 7 8 /// <summary> 9 /// 几期方案 10 /// </summary> 11 public int PeriodScheme{ set; get; } 12 13 /// <summary> 14 /// 选择的位数,比如选中个位和十位 15 /// </summary> 16 public string ChoosePositions { set; get; } 17 18 public Play(List<string> prizeNums,int period,string choosePositions) 19 { 20 this.PrizeNums = prizeNums; 21 this.PeriodScheme = period; 22 this.ChoosePositions = choosePositions; 23 } 24 public abstract Dictionary<string,double> ComputePrizeRate(); 25 }
接下来是具体的实现类:
1 public class ThreeCodePlay : Play 2 { 3 public ThreeCodePlay(List<string> prizeNums, int period, string choosePositions) 4 : base(prizeNums, period, choosePositions) 5 { 6 7 } 8 9 public override Dictionary<string, double> ComputePrizeRate() 10 { 11 var rates = new Dictionary<string, double>(); 12 //获取三码的所有数 (0,1,2)-(7,8,9) 13 //计算中奖率 14 return rates; 15 } 16 }
还有4码,五码,一直到九码,都需要实现。这里我就不赘述了。有兴趣的读者可自行实现。
接下来,我们需要一个决策类,这个类直接负责和客户端打交道。
1 public class PlayContext 2 { 3 private Play play; 4 5 public PlayContext(Play play) 6 { 7 this.play = play; 8 } 9 public Dictionary<string, double> GetPrizeRate() 10 { 11 return play.ComputePrizeRate(); 12 } 13 }
客户端调用:
1 public class Client 2 { 3 public Dictionary<string, double> GetPrizeRate() 4 { 5 Play play = new ThreeCodePlay(new List<string>() { "01005" }, 3, "00011"); 6 PlayContext context = new PlayContext(play); 7 return context.GetPrizeRate(); 8 } 9 }
有读者读到这儿,觉得这个 Playcontext类是不是多余的呢?我客户端既然知道具体的玩法类,我可以直接调用玩法类计算中奖率,何必还要通过你PlayContext呢?再说了,你PlayContext类什么都没做啊。其实策略模式当中的算法就和我们公司的所有普通员工一样,直接上级领导类似PlayContext。其实PlayContext对play对象做了一个封装,封装使客户端和play类算法调用解耦,试想下,如果客户端直接调用play对象的计算方法,那么,如果play对象的算法名改变,那么我们是不是得改客户端的代码了。一个客户端好办,如果是很多个客户端,那么就是灾难。另外PlayContext类还可以添加其它方法,比如,客户端想要知道,3码的数一共是多少个,等等。
有读者可能觉得,你这个策略模式,明明需要客户端知道有哪些玩法,不等于还是把具体的玩法类暴露出来了。是的,为了进一步隔离客户端和玩法类,把构造玩法对象,放到PlayContext中,PlayContext根据条件,自行构造玩法对象。这不就成了简单工厂模式了吗?问题来了,简单工厂模式和策略模式到底有什么不同呢?
1、关注点不同:工厂模式封装的是复杂对象的创建,而策略模式封装的是一个对象的多种行为。PlayContex如果作为工厂,肯定返回一个play对象,如果作为策略,肯定执行一个Play对象的行为。
2、策略模式可以使客户端避免直接接触算法的一些细节,工厂模式可以使客户端不必关心对象的构造过程。
再举个额外的例子:
比如,我们下班回家有多种途径,1、坐公交 2、骑自行车 3、网约车 4、出租车 5、摩的 6、开车 7、走路 8、跑步 9、骑马 10、地铁 ,如果我们把回家看作一个对象的话,这个对象的行为方式实在多变。倒不是说,回家这个对象构造有多么复杂,而是我们能不能根据交通情况,自由地选择回家模式。
比如,在天空中可以飞的东西:1、飞机 2、鸟 3、风筝 4、气球 5、子弹 6、弓箭 7、孔明灯 8、 云朵 9、 雪花 10、ufo,如果我们把这些东西制造出来,是不是可以飞呢?倒不是说飞有多么难,关键是制造这些东西比较难,而且工艺都不同。
再回到我们玩彩票上来,倒不是玩彩票有多难,而是玩彩票的玩法实在太多,不同的玩法,意味着,中奖率的计算方法是不同的。所以这里着重是行为方式,用策略模式是恰当的选择。