在平时生活中当我们想要做一件事的时候往往会有许多的途径和方法,像我们去公司上班,可以走路去,也可以骑车或者开车去;还有像吃饭,我们可以选择自己做饭吃,也可以出去吃,脸长得好看的还能让人请吃饭等等,但无论选择哪种方式,我们最终达到的目的结果都是一样的,只是过程不一样。在面向对象的程序设计中,我们就可以把这种变化的行为隔离封装,实现代码的高度扩展和复用,也就是我今天要讲的策略模式。
在探讨策略模式之前,我们需要了解几个OO原则:
封装变化不用多说,如果不从固有的代码中将变化的代码抽离出来,那将会让程序变得非常臃肿; 那为什么要多用组合,少用继承呢?继承不是OO的基础吗?我们来看一个例子:假如有一只绿头鸭和红头鸭,他们都会跑,也都会叫,因此可以抽象出一个Duck类,有run和quack方法,看似没有什么问题,但是当某一天出现了一只木头鸭,它也需要继承Duck类,但是它不会叫也不会跑啊,咋办呢?当然,我们可以利用方法重写,在子类中将父类的行为覆盖掉;这是一个方法,不过我们可能还会出现会飞的野鸭、火箭玩具鸭等等,怎么办呢?难道去修改父类,在父类中增加一个fly方法么?那之前不会飞的鸭子也都可以满天飞了,至此,我们看到了滥用继承所带来的困扰,也发现了鸭子的行为对于鸭子来说是多变的,因此我们可以将鸭子的行为抽象出来为Behavior接口(也就是针对接口编程),并利用组合将行为织入到鸭子中,就可以解决上述的问题了。这就是策略模式的核心,来看看《Head First》对其的定义:
策略模式定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
了解了策略模式的定义,我们再来看看如何利用代码实现该模式。想象一个冒险游戏,将其分为角色和武器行为两大类,角色可以更换任意一种武器来攻击敌人。
public abstract class Character {
WeaponBehavior weaponBehavior;
public void setWeaponBehavior(WeaponBehavior weaponBehavior) {
this.weaponBehavior = weaponBehavior;
}
public void fight() {
weaponBehavior.userWeap();
}
}
public class King extends Character {
public King(WeaponBehavior weaponBehavior) {
this.weaponBehavior = weaponBehavior;
}
}
public interface WeaponBehavior {
void userWeap();
}
public class SwordBehavior implements WeaponBehavior {
@Override
public void userWeap() {
System.out.println("用宝剑挥舞!");
}
}
public class KnifeBehavior implements WeaponBehavior {
@Override
public void userWeap() {
System.out.println("用匕首刺杀");
}
}
public static void main(String[] args) {
Character c = new King(new AxeBehavior());
c.fight();
c.setWeaponBehavior(new SwordBehavior());
c.fight();
}
在父类角色中,利用组合将武器行为赋予给角色,在角色攻击时实际是将攻击行为委托给WeaponBehavior的,同时需要注意,这里使用的是WeaponBehavior接口,而不是具体的某种武器,这样我们在程序运行时需要使用哪种武器就只需要调用setter方法重新设置他需要的武器即可,这也就是针对接口编程,不针对实现编程。
策略模式非常的简单,也非常的有用,可以将变化的算法从程序中解耦出来,在程序设计之初时,我们就可以仔细思考发现这部分变化,以便将来的扩展。但策略模式也不是完美的,客户端需要知道所有的策略类以及区别,并自行选择使用哪一种,同时也会造成大量的策略类和对象,导致系统结构越来越庞大,对象数量过多也会占用大量的内存,可以使用享元模式来减少对象的数量。