文章内容参考<Head First设计模式>
我们以一个实例 来具体了解策略模式
公司有一个非常成功的赛车游戏,游戏中玩家会驾驶各种赛车,该游戏内部设计的时候设计了一个赛车超类(Racing),并让各种鸭子继承该超类
Run() |
---|
SpeedUp() |
Display() |
每种赛车都会跑(Run),也会加速(SpeedUp),其中Display是抽象方法,因为每种赛车外观不同
为了增加游戏真实性,高管们决定在游戏中给赛车加燃料,并把该任务分配给了Joe
此时的Joe想法是这样的:我只需要在Racing类中加入Fueling()方法,所有的赛车都会继承Fueling方法,展示我技术的时候到了!
但是问题出现了
在展示中,一辆自行车竟然也可以加燃料!!!
为什么会出现这种问题呢??
Joe忽略了一件事,并非Racing所有的子类都需要加燃料,如果直接在Racing加入此方法,会使某些不适合该行为的子类也具有该方法,对代码的局部修改,影响层面不只是该局部!
Joe想到了一个对策,我可以在子类中Fueling()方法将父类覆盖掉,可是问题又出现了,如果加入三轮车,又会如何?
Joe意识到了,可能继承不是一个很好的对策,因为每当新赛车出现,他都要被迫去检查并覆盖Fueling方法,简直是个噩梦
Joe又想出了一个新对策,我可以将Fueling从超类中取出,放进一个FuelingAble接口中,这么一来只有需要加燃料的赛车才实现此接口
你觉得他的方法如何呢?
该方法一开始似乎不错,但是仔细一想,每次继承接口都需要实现它,相同的代码可能会出现多次,并且修改代码时,必须要找到具体实现的类中,一不小心就会出现新的问题
此时此刻,策略模式将拯救Joe于水火之中!!!
设计原则1:把会变化的部分单独取出并封装起来,以便以后可以轻易的改动或扩充此部分,而不影响需要变化的其他部分
是时候处理Fueling方法了!
首先我们将Fueling方法从Racing超类中取中,顺便让Fueling方法可以动态的改变那是更好不过的了
设计原则2:针对接口编程,而不是针对实现编程
我们利用接口代表每个行为 例如:FuelingBehavior(添加燃料行为)而我们用具体的行为类来实现这个接口而不是在具体的赛车类内实现
燃料添加分为加汽油,打气,充电
为什么不用抽象超类,而是使用接口进行处理操作呢?
如果我们使用抽象超类时声明变量依然需要具体类型,仍然是针对实现进行编程
Car c=new Car() ;
c.Fueling();
但是如果针对接口进行编程的做法会如下
在此我们有三个类实现具体的行为汽油(Gasonline),打气(CheerUp),充电(Charge)
FuelingBehavoir fue=new Gasoline(); //利用fue进行多态的调用
fue.Fueling(); //我们不知道具体的子类型是什么 我们只关系它如何正确进行Fueling()方法即可
这样的设计可以让加燃料的内容被其他的对象服用,因为他们是独立存在的并且与赛车类无关
而我们也可以增加一些行为,不会影响别的类
当你将两个类结合起来使用,如同本例一般,就是组合,这种做法和继承不同的地方在于,赛车的行为不是继承而来,而是和适当的行为对象组合来的
设计原则3:多用组合,少用继承,“有一个”可能比“是一个”更好
好了,是时候完善我们的程序了!
定义加燃料接口和其具体实现行为类
定义基类和它的派生类 定义接口类型,利用派生和多态实现具体行为
我们进行测试
我们可以通过替换类型来动态更改赛车适配的加燃料方式
目前的做法弹性是可以的,只是初始化实例变量的做法还是对具体实现编程不够弹性,后续内容中会有更多的模式可用,到时候就可以修正这一点了
最后我们总结一下策略模式
OO基础:抽象,封装,多态,继承
OO原则:封装变化
多用组合,少用继承
针对接口编程,不针对实现编程
策略模式:定义了算法族,分别封装起来,让他们之间可以互相替换,此模块让算法的变法独立于其他算法的用户