前言
策略模式定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。
正文
《head first设计模式》给的例子很好,它通过改造了一个Duck功能,将“行为”抽象成接口,每一个接口及其实现类组成一个算法组,每个算法组中的算法可以无缝替换。
通过这个例子,还告诉了我们一个道理:“有一个”关系可能比“是一个”关系更好。
让我们来看这个例子,有一个Duck父类,里面有一些行为方法,具体的鸭子类实现接口。
class Duck {quack();swim();display();
}
具体的鸭子:
class MallardDuck {display() {// 具体实现}
}
但是现在我们想给Duck类加功能,比如让鸭子能飞,于是我们自然想的是在父类中加一个fly()方法:
class Duck {quack();swim();display();fly(); // 在父类新增方法
}
但是,并不是所有鸭子都能飞。当代码设计到维护时,为了复用目的而使用继承,结局并不完美。
于是开发者又想到一招,将fly()和quack()方法抽成接口,让实现类去实现这2个接口。但是这样每当有新的鸭子子类出现时,都要检查是否要实现这个接口,而且重复代码会增多。
此时引出第一个设计原则:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
我们知道Duck类中的fly()和quack()会随着鸭子的不同而不同,为了把这两个行为从Duck类中分开,我们将它们从Duck类中取出来,建立一组新类来代表每个行为。
我们希望一切能够有弹性,换句话说,我们应该在鸭子类中加一个设定行为的方法,这样就可以在运行时动态地改变鸭子的行为。
此时引出第二个设计原则:针对接口编程,而不是针对实现编程。
我们利用接口代表每个行为,比如FlyBehavior与QuackBehavior,而行为的每个实现类都将实现其中一个接口。这次鸭子类不再负责实现Flying与Quacking接口,反而是由我们制造一组其他类专门实现FlyBehavior与QuackBehavior,称为行为类。这和以前的做法迥然不同,以前的做法是:行为来自Duck超类的具体实现类,或者继承某个接口并由子类自行实现而来,这两种方式都是依赖于“实现”,我们被实现绑得死死的,没有办法更改行为。
在新设计中,鸭子的子类将持有接口所表示的行为,所以实际的“实现”不会被绑死在鸭子类中。我们来看新设计是什么样的:
首先定义行为,下面是2组行为,也称算法组:
interface FlyBehavior {fly();
}class FlyWithWings implements FlyBehavior {fly() {// 实现鸭子飞行}
}class FlyNoWay implements FlyBehavior {fly() {// 什么都不做,不会飞}
}
另一组:
interface QuackBehavior {quack();
}class Quack implements QuackBehavior {quack() {// 实现鸭子呱呱叫}
}class Squeak implements QuackBehavior {quack() {// 橡皮鸭吱吱叫}
}class MuteQuack implements QuackBehavior {quack() {// 什么都不做,不会叫}
}
我们再来整合鸭子的行为,关键在于:鸭子现在会将飞行和呱呱叫的动作“委托”给别人处理,而不是使用定义在Duck类(或子类)内的呱呱叫和飞行方法。
首先在Duck类中加入两个成员变量
public class Duck {FlyBehavior flyBehavior;QuackBehavior quackBehavior;public void performQuack() {quackBehavior.quack();}public void performFly() {flyBehavior.fly();}// 动态设定行为public void setFlyBehavior(FlyBehavior fb) {this.flyBehavior = fb;}public void setQuackBehavior(QuackBehavior qb) {this.quackBehavior = qb;}
}
添加测试类:
public static void main(String[] args) {Duck model = new ModelDuck();model.performFly();model.setFlyBehavior(new FlyRocketPowered());model.performFly();
}