以下通过概念、需求场景、需求分析、代码设计及实践、UML类图展示,一步步给大家介绍 “策略模式”
一、策略模式概念
策略模式:定义算法族,将其分别封装起来,让它们之间可以相互替换。此模式让算法的变化独立于使用算法的客户
二、需求场景
老板下达任务,今日之内设计一个游戏,模拟各类鸭子的行为。鸭子有好几个种类,而且每种鸭子的外观、游泳技能、飞行技能、叫声都不一样。
具体要求如下:
1、鸭子种类:野鸭(Mallard duck)、红头鸭(Red Head duck)、橡皮鸭(Rubber duck)、诱饵鸭(Decoy Duck)
3、野鸭的外观是绿头, 红头鸭的外观是红头,橡皮鸭的外观是橡皮, 诱饵鸭的外观是木头
2、所有鸭子都会游泳
3、野鸭、红头鸭会飞,但橡皮鸭、诱饵鸭不会飞
4、野鸭、红头鸭的叫声是“Quack Quack ...”,而橡皮鸭的叫声是“Squeak Squeak....”,诱饵鸭不会叫
三、需求分析
【tips:设计原则描述请见文章末尾】
方法一:Duck中提供display、swim、fly、quack方法, 子类继承
分析:所有的子类都会有父类的行为,这和需求是不符的,且鸭子的行为在子类中会根据需求不断地被重写,代码无法复用,此方法不可行。
方法二:根据设计原则一,将变化和不会变化的部分分开,由于Duck类内的fly和quack会随着鸭子的不同而改变,我们可以取出fly和quack行为,分别建立新类,每组类实现各自的动作。
分析:鸭子的fly和quack行为被放在分开的类中,那么我们再对这两个行为进行分析:
1、鸭子的fly和quack行为分别可能有多种情况,根据设计原则二,我们还可以封装FlyBehavior和 QuackBehavior接口,具体的行为编写在实现了这两个接口的类中
2、然后在Duck父类中包含设定fly和quack行为的方法(performFly、performQuack),这样,Duck父类就不再需要知道行为的实现细节,而子类将可以使用这两个接口所表示的行为
3、为了让这两个行为可以动态地改变,并且可以在运行时动态地改变子类鸭子的飞行行为(setFlyBehavior、setQuackBehavior)
嗯,这是个好方法,很完美地利用了策略模式!下面我们就用这个方法来实践一下
四、代码设计及实践
根据以上对方法二的分析,下面进行代码设计及实践:
1、首先设计两个接口,FlyBehavior、QuackBehavior,还有它们对应的类,负责实现具体的行为
展示具体代码:
FlyBehavior接口及其实现
public interface FlyBehavior {
public void fly();
}
public class FlyRocketPowered implements FlyBehavior {
public void fly() {
System.out.println("I'm flying with a rocket");
}
}
public class FlyWithWings implements FlyBehavior {
public void fly() {
System.out.println("I'm flying!!");
}
}
public class FlyNoWay implements FlyBehavior {
public void fly() {
System.out.println("I can't fly");
}
}
FlyBehavior接口及其实现
public interface QuackBehavior {
public void quack();
}
public class MuteQuack implements QuackBehavior {
public void quack() {
System.out.println("<< Silence >>");
}
}
public class Quack implements QuackBehavior {
public void quack() {
System.out.println("Quack");
}
}
public class FakeQuack implements QuackBehavior {
public void quack() {
System.out.println("Qwak");
}
}
public class Squeak implements QuackBehavior {
public void quack() {
System.out.println("Squeak");
}
}
2、在Duck父类中加入“FlyBehavior”和“QuackBehavior”两个实例变量,并加入“performFly”、“performQuack”方法,然后在子类构造器调用具体的实现,这里用MallardDuck进行举例。
Duck,java
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck() {
}
public void performFly() {
flyBehavior.fly();
}
public void performQuack() {
quackBehavior.quack();
}
abstract void display();
public void swim() {
System.out.println("All ducks float, even decoys!");
}
}
MallardDuck.java
public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
public void display() {
System.out.println("I'm a real Mallard duck");
}
}
使用 MiniDuckSimulator.java 测试一下MallardDuck
public class MiniDuckSimulator {
public void MiniDuckSimulator() {
MallardDuck mallard = new MallardDuck();
mallard.performFly();
mallard.performQuack();
}
}
3、为了让这两个行为可以动态地改变,在鸭子子类中动态设定方法(setFlyBehavior、setQuackBehavior),而不是在构造器内实例化。此后,我们可以随时调用这两个方法来改变鸭子的行为
在Duck.java中加入两个新方法
public void setFlyBehavior(FlyBehavior fb) {
flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb) {
quackBehavior = qb;
}
制造一个新的鸭子类型:模型鸭(ModelDuck.java)
public class ModelDuck extends Duck {
public ModelDuck() {
flyBehavior = new FlyNoWay();
quackBehavior = new Quack();
}
public void display() {
System.out.println("I'm a model duck");
}
}
使用 MiniDuckSimulator.java 测试一下ModelDuck
public class MiniDuckSimulator {
public void MiniDuckSimulator() {
Duck model = new ModelDuck();
model.performFly();
model.setFlyBehavior(new FlyRocketPowered());//改变飞行行为
model.performFly();
}
}
五、UML类图展示
最后,我们使用UML类图来看看整体的格局
本设计模式参考的3个设计原则:
设计原则一:在应用中找出可能需要变化的地方,把它们独立封装起来,以便对其他部分不造成影响。
说明:比如,如果每次来一个新的需求,某块的代码都会发生变化,那么这部分的代码就要被抽取独立出来,和其他较为稳定的代码进行区分。
设计原则二:针对接口编程,而不是针对实现编程。
说明:这里的“接口”不是单独指定接口类型,而是一个概念,即 “超类型”,即抽象类或接口等。针对接口编程的关键在于多态,使用“超类型”去声明变量时,只要是实现了此超类型的类对应的对象,都可以指定给这个变量。
设计原则三:多用组合,少用继承
说明:使用组合建立系统具有很大的弹性,不仅可将行为算法族封装成类,更可以在运行时动态地改变行为