策略模式的核心思想,我们称之为:搭配行为。
就是说对于一个类,我们可以动态的去给他搭配一个行为,重点是,我们还可以在需要的时候,去替换他的行为。
按照我们以往的思维,行为就是方法。方法要如何搭配和替换呢?
我们以经典的对战游戏--魔兽争霸为例,来开始我们的策略模式探索之旅。
魔兽争霸中有一个英雄,叫剑圣。剑圣和其他英雄一样,有一个行为,叫移动。那么,如果要我们去设计英雄及其行为的话,应该首先会有一个英雄超类Hero,应该是抽象类或接口,以让不同的英雄自己去实现行为。我们只以移动这个行为为例,让剑圣去继承Hero,实现移动的行为,下面是Hero和BladeMaster的代码:
public abstract class Hero
{
public abstract void move();
}
public class Blademaster implements Hero
{
@Override
public void move()
{
System.out.println("blademaster moving...");
}
}
我们以一句简单的输出来模拟剑圣的移动行为。
如果是其他英雄,我们也让其继承Hero,多态的实现,目前来看,谁能说这样的设计不好呢?
现在问题来了,我们知道,剑圣有一个技能,叫疾风步,当使用了疾风步技能之后,剑圣将进入隐身状态,并且移动速度大大加快,也就是说,他的移动这个行为发生了改变。那我们如何应付移动行为改变这个事情呢?方法是写死的,我们不可能在程序运行过程中修改方法吧。请别忘了,玩家可能在程序运行的任何时候去使用剑圣的疾风步。
你可能想到创建一个记录状态的变量fasterMoving,当使用了疾风步时,改变这个变量的状态,然后在move方法里面,根据这个变量的不同状态,去做不同的行为:
public class Blademaster extends Hero
{
private boolean fasterMoving = false;
public void setFasterMoving(boolean isFaster)
{
this.fasterMoving = isFaster;
}
@Override
public void move()
{
if(!fasterMoving)
{
System.out.println("blademaster moving...");
}else
{
System.out.println("Blademaster faster moving...");
}
}
}
又是if..else, 如果以后移动行为又有了第三种状态,又要继续在这里添加一个else吗?
请记住,修改是设计的大忌。记住开闭原则--对扩展开放,对修改关闭。就是说尽量不要去修改原有的代码。
在设计模式中,有一个重要的原则,就是将变化的部分分离出来,不要和不变的部分混在一起。在这里,move方法成了一个变化的因素,所以,我们将move方法分离出来,好让其他部分更稳定。
那么分离出来的行为该怎么设计呢?
既然分离,那就分的彻底,将移动这个行为交给别的对象去做,我们称为委托。委托别的对象实现移动这个行为。或者更形象一点,把移动这种麻烦的事情,外包给别人去做。
而英雄类自己,只需要认识一个提供移动这种行为的对象就行了。
就是这样了,在英雄类中增加一个成员变量,而这个成员变量就是帮忙完成移动行为的,具体请看代码:
public class Hero
{
private MoveBehavior moveBehavior;
public void move()
{
moveBehavior.move();
}
public void setMoveBehavior(MoveBehavior behavior)
{
this.moveBehavior = behavior;
}
}
public class Blademaster implements Hero
{
public Blademaster()
{
this.moveBehavior = new BMGeneralMoveBehavior();
}
}
增加了一个成员moveBehavior,并提供set方法。当move方法被调用时,实际上程序会去调用moveBehavior的move方法。以下是MoveBehavior接口以及两个实现类的代码:
public interface MoveBehavior {
public void move();
}
public class BMGeneralMoveBehavior implements MoveBehavior {
//此类表示剑圣正常的移动行为
@Override
public void move() {
System.out.println("Blademaster moving...");
}
}
public class BMFasterMoveBehavior implements MoveBehavior {
//此类表示剑圣使用疾风步时的移动行为
@Override
public void move() {
System.out.println("Blademaster faster moving...");
}
}
这样做的意义是,当剑圣使用疾风步时,程序会调用剑圣对象的setMoveBehavior方法,将moveBehavior成员换成一个BMFasterMoveBehavior的对象,当再次调用move方法是,剑圣表现出来的就是BMFasterMoveBehavior的移动行为了。现在,在英雄类中,move方法不用变化了,需要变化的现在是成员moveBehavior。
以下是一段模拟游戏中剑圣移动行为的代码,我们添加了一个监听的功能,监听游戏中玩家使用了某些功能键,就可以触发移动行为或开启疾风步技能,我们定义一个actionPerformed方法,当监听到功能键时,会调用这个方法,并判断按下的是移动功能键还是疾风步功能键,如果是移动功能键,将调用move方法(本质上是调用moveBehavior的move方法),如果是疾风步功能键,将调用setMoveBehavior方法,重新设置moveBehavior:
public void actionPerformed(String key){
if("move".equalsIgnoreCase(key)){
blademaster.move();
}else if("faster".equalsIgnoreCase(key)){
blademaster.setMoveBehavior(new BMFasterMoveBehavior());
}
}
这样,在按完疾风步键之后的移动行为,都表现为BMFasterMoveBehavior中所定义的快速移动的方式。还有,当疾风步结束后,也会调换setMoveBehavior方法,把moveBehavior设置成BMGeneralMoveBehavior对象:
blademaster.setMoveBehavior(new BMGeneralMoveBehavior());
随机应变,根据不同的情况,设置不同的行为方式,这就是策略。
定义策略模式:
策略模式定义了算法族,分别封装起来,让他们之间可以互相替换。此模式让算法的变化独立于使用算法的客户。
所谓互相替换,就是我们例子中调用set重新设置行为的过程。
值得一提的另外一个概念,是面向对象中的组合,组合我们经常遇到,但可能并没有认真考虑过他的好处。
我们的例子中,Hero对象中,有一个成员,是MoveBehavior对象,面向对象中这种两个对象一个被另一个所引用或互相引用的情况,就叫组合。组合的意义在于对象之间互相协作,帮助。策略模式便是将组合的魅力发挥的淋漓尽致的一种设计模式。
看完本文,如果你能回过头,重新理解本文开头“搭配行为”四个字的含义,那你便已经掌握了策略模式的真谛!