设计模式是被前人发现、经过总结形成了一套某一类问题的一般性解决方案。使用模式最好的方式是:把模式装进脑子,然后在设计和已有的应用中,寻找何处可以使用它们。以往是代码复用,现在是经验复用。
从模拟鸭子游戏说起
初始需求:各种鸭子(野鸭MallardDuck、红头鸭RedheadDuck等),一边游泳swim,一边呱呱叫quack,每种鸭子外观都不同display。
初始方案:设计一个鸭子超类,并让各种鸭子继承此超类。
需求变更:让鸭子能够飞fly。
方案1:在Duck类中加上fly()。
结果:所有鸭子都继承fly,但并非所有鸭子都会飞,比如橡皮鸭RubberDuck(继承牵一发动全身)。
方案1_1:对于不能飞的鸭子,在子类中覆盖fly()。
结果:每当有新的鸭子出现,就要被迫检查并可能需要覆盖fly()。同理,对于叫声quack也一样。
方案2:把fly()和quack()从超类中取出,放进Flyable和Quackable接口中,只会飞的鸭子实现Flyable,会叫的实现Quackable。
结果:不会再有会飞的橡皮鸭,但由于接口不具有实现代码,造成代码无法复用(接口无法达到代码复用)。
方案2_1:设计两个接口FlyBehavior和QuackBehavior,还有它们对应的类,负责实现具体行为。
结果:飞行和呱呱叫动作可以被其他的对象复用,因为这些行为已经与鸭子类无关;可以新增一些行为,不会影响到既有的行为类,也不会影响使用飞行行为的鸭子类(有了继承的复用好处,没了继承所带来的包袱)。
完善方案2_1:在Duck类中加入两个变量“flyBehavior”和“quackBehavior”,声明为接口类型,用performFly和performQuack分别取代fly和quack;同时加入两个新方法setFlyBehavior和setQuackBehavior,用来随时改变鸭子的行为。
测试Duck的代码
Duck类(Duck.java)
public abstract class Duck { //所有鸭子子类都继承它 FlyBehavior flyBehavior; QuackBehavior quackBehavior; public Duck(){ } public abstract void display(); public void perfmormFly(){ //委托给行为类 flyBehavior.fly(); } public void performQuack(){ //委托给行为类 quackBehavior.quack(); } public void setFlyBehavior(FlyBehavior fb){ flyBehavior=fb; } public void setQuackBehavior(QuackBehavior cb){ quackBehavior=cb; } public void swim(){ System.out.println("All ducks float,even decoys!!"); } }
MallardDuck类和ModelDuck类(MallardDuck.java与ModelDuck)
public class MallardDuck extends Duck{ public MallardDuck(){ quackBehavior=new Quack(); flyBehavior=new FlyWithSwings(); } public void display(){ System.out.println("I'm a real Mallard Duck"); } } public class ModelDuck extends Duck{ public ModelDuck(){ flyBehavior=new FlyNoWay(); quackBehavior=new MuteQuack(); } public void display(){ System.out.println("I'm a model duck!!"); } }
FlyBehavior接口(FlyBehavior.java)与三个行为实现类(FlyWithWings.java、FlyNoWay.java和新增的FlyRocketPowered.java)
public interface FlyBehavior { public void fly(); } public class FlyWithSwings 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!!"); } } public class FlyRocketPowered implements FlyBehavior{ public void fly(){ System.out.println("I'm flying with a rocket!!"); } }
QuackBehavior接口(QuackBehavior.java)及其三个实现类(Quack.java、MuteQuack.java、Squeak.java)
1 public interface QuackBehavior { 2 public void quack(); 3 } 4 5 public class Quack implements QuackBehavior{ 6 public void quack(){ 7 System.out.println("Quack!!"); 8 } 9 } 10 11 public class MuteQuack implements QuackBehavior{ 12 public void quack(){ 13 System.out.println("Silence!!"); 14 } 15 } 16 17 public class Squeak implements QuackBehavior{ 18 public void quack(){ 19 System.out.println("Squeak"); 20 } 21 }
测试类(MiniDuckSimulator.java)
public class MiniDuckSimulator { public static void main(String[] args) { // TODO Auto-generated method stub Duck mallard=new MallardDuck(); mallard.perfmormFly(); mallard.performQuack(); Duck model=new ModelDuck(); model.perfmormFly(); model.performQuack(); model.setFlyBehavior(new FlyRocketPowered()); model.perfmormFly(); } } /* * * 运行结果: * Quack!! * I can't fly!! * Silence!! * I'm flying with a rocket!! */
这就是策略模式
策略模式(Strategy Pattern)定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。封闭可以互换的行为,并使用委托来决定要使用哪一个。
策略模式结构
Strategy:可由接口或抽象类来实现;定义所有支持的算法的公共接口;Context使用这个接口来调用某ConcreteStrategy定义的算法。
ConcreteStrategy:具体的策略实现,也就是具体的算法实现。
Context:用一个ConcreteStrategy对象来配置;维护一个对Strategy对象的引用;可定义一个接口来让Strategy访问它的数据。
策略模式示例代码
/** * 策略,定义算法的接口 */ public interface Strategy { // 某个算法的接口,可以有传入参数,也可以有返回值 public void algorithmInterface(); } /** * 实现具体的算法 */ public class ConcreteStrategyA implements Strategy { public void algorithmInterface() { //具体的算法实现 } } public class ConcreteStrategyB implements Strategy { public void algorithmInterface() { //具体的算法实现 } } public class ConcreteStrategyC implements Strategy { public void algorithmInterface() { //具体的算法实现 } } /** * 上下文对象,通常会持有一个具体的策略对象 */ public class Context { //持有一个具体的策略对象 private Strategy strategy; // 构造方法,传入一个具体的策略对象 //@param aStrategy 具体的策略对象 public Context(Strategy aStrategy) { this.strategy = aStrategy; } // 上下文对客户端提供的操作接口,可以有参数和返回值 public void contextInterface() { //通常会转调具体的策略对象进行算法运算 strategy.algorithmInterface(); } }
关于策略模式, 最后推荐一篇博文:http://www.uml.org.cn/sjms/201009092.asp