# 设计模式之策略模式(小鸭子的故事) ## 引言 当我们完成一个复杂的业务中常常会面临一个问题:针对一个对象的某个行为,不同的情境下有不同的处理方式; 就比如今天我要去上班,那么我需要以哪种交通方式去上班呢?可以有下面几种选择: * 步行 * 公交 * 地铁 * 自行车 * 开车 当然还会有更多的选择,这只是列举了几种;我上班时会在不同的情况下选择不同的交通工具,这就是不同处理方式; 如果在代码中体现,我们可以选择用 if…else 或者 switch 来对不同的情境进行判断,从而选择相应的交通工具;这样想当然很简单,但是写出来的代码会很复杂,而且后期进行维护是很难的; 那么就需要想一个办法将这个对象和这个行为分开,这个行为就是一个算法,这样对于修改维护只需要针对这个算法就可以了; 这里就需要用到设计模式中的策略模式; 策略模式定义: **策略模式(Strategy Pattern)中,定义算法族(策略组),分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户;** 下面我将会用一个模拟鸭子的例子来介绍这个模式; ## 张三的问题 某公司有一个模拟鸭子的业务,这个业务中将会定义多种不同类型的鸭子,有的会飞,有的会叫,并且飞行方式和叫声可能也会有不同;现在这个业务交给了张三去做,张三以熟练的OO技术,设计了一个父类为Duck,然后让各种不同的鸭子去继承这个父类,便轻而易举的完成了这个项目; 类图:  可以看到,红头鸭和绿头鸭都继承了Dunk并重写了display方法,是不是很简单,我们看看代码: Duck类: ``` // 鸭子类(抽象类) public abstract class Duck { // 抽象方法:显示什么鸭子 public abstract void display(); // 飞行的方法 public void fly() { System.out.println("I'm flying!!!"); } // 叫的方法 public void quack() { System.out.println("嘎嘎嘎"); } } 12345678910111213 ``` MallardDuck类 ``` // 绿头鸭 public class MallardDuck extends Duck{ @Override public void display() { System.out.println("我是一只绿头鸭!!"); } } 1234567 ``` RedheadDuck类 ``` // 红头鸭 public class RedheadDuck extends Duck{ @Override public void display() { System.out.println("我是一只红头鸭!!"); } } 1234567 ``` 输出测试一下: ``` // 测试相应的功能 public class MiniDuckSimulator { public static void main(String[] args) { Duck mallardDuck = new MallardDuck(); // 绿头鸭 mallardDuck.display(); mallardDuck.fly(); mallardDuck.quack(); System.out.println("-----------------"); Duck redheadDuck = new RedheadDuck(); // 红头鸭 redheadDuck.display(); redheadDuck.fly(); redheadDuck.quack(); System.out.println("-----------------"); } } 12345678910111213141516 ``` 结果如下: ``` 我是一只绿头鸭!! I'm flying!!! 嘎嘎嘎 ----------------- 我是一只红头鸭!! I'm flying!!! 嘎嘎嘎 ----------------- 12345678 ``` 就在张三沾沾自喜的时候,这时产品经理提出了新的要求:要加一种新的鸭子:橡皮鸭; 张三一想:橡皮鸭不能飞,而且橡皮鸭的叫声是“吱吱吱”,那么只要创建一个橡皮鸭的类,依然继承Dunk,然后重写Dunk中的fly和quack方法不久可以了嘛; 于是他又加了一个类: RubberDuck类 ``` // 橡皮鸭不会飞,叫声是吱吱吱,所以需要重写所有方法 public class RubberDuck extends Duck{ @Override public void display() { System.out.println("我是一只橡皮鸭!!"); } @Override public void fly() { System.out.println("I can't fly!!!"); } @Override public void quack() { System.out.println("吱吱吱"); } } 123456789101112131415 ``` 这时张三突然意识到,这里使用继承好像并不是特别完美,虽然实现了功能,但是RubberDuck类把父类中的所有方法都重写了一遍,这样的代码就出现了重复啊; 张三又想: Duck类中的fly和quack好像并不适用于所有鸭子的情况,有的鸭子不会飞,有的不会叫,有的叫声不一样,有的飞行方式不一样。。。。这样看来,这个父类并不是完美的;如果产品经理让我给鸭子多加一个游泳的行为,那么一旦加到Duck类中后,所有种类的鸭子可能都会面临修改的可能,这样也太麻烦了吧!那该怎么办呢? 这里总结一下张三通过继承来提供Duck行为存在的问题: * 代码在多个子类中重复 * 运行时的行为不易改变 * 很难知道不同的鸭子具体的行为 * 改变会牵一发动全身,造成其他种类鸭子不需要的改变; ## 该怎么做? 这时张三突然想到:可以使用一个Flyable和一个Quackable**接口**,只让会飞的鸭子实现Flyable,会叫的鸭子实现Quackable接口不就可以了嘛; 真的可以吗? 这样的话就会重复的代码会更多,如果需要修改飞行的行为,那么就需要对所有实现飞行接口的代码进行修改;一旦需要加入新的行为,如果用接口,那就需要对所有的鸭子进行一个判断并实现该行为; 因为在这里张三只声明了三种类型的鸭子,如果是五十种呢?一百种呢?难道都需要一一修改吗? > 其实出现这些问题的本质就是因为鸭子Duck的行为会在子类里不断地改变,并且如果让所有的子类都有这些行为也是不现实的; > > 且使用接口不能实现代码,就无法达到代码的复用,一旦修改了某个行为,就需要找到所有实现该行为的类去修改,不仅工作量更大,而且可能会出现新的错误; 这时李四给张三提建议: **只需要找到项目中可能需要变换的地方,并把这个变化独立出来,不和那些不会变化的代码混在一起不就可以了吗?** 李四的意思其实就是:把Dunk中会变化的部分取出来,单独封装起来,这样就可以轻易实现更该和扩充该部分,且不会影响不会变化的内容; 那么下面张三需要做的就是:将鸭子变化的行为从Duck中取出封装起来了; ## 问题解决 张三对Duck进行一个分析:既然要分离变化的行为,那么在这个类中也就只有fly和quack行为会改变了,所以只需要把这俩行为拿出来然后封装起来就可以了; 这时又有了一个新的问题: 张三希望代码更有弹性,因为开始的代码没有弹性才这样做的,如果能够动态的改变鸭子的行为,那样一旦有需求改变肯定会容易很多; 张三灵机一动,想到了一个设计原则: **面向接口编程,而不是针对实现编程;** 那么就是用接口来抽象这个行为,具体行为的表现模式实现这个接口就可以了; 所以张三准备了一个QuackBehavior接口和一个FlyBehavior接口,然后将他们聚合到Duck类中,这样就可以灵活的修改代码了; 由于产品经理提出了新的需求:增加一个不会飞不会叫的**模型鸭**,并且给它加一个**火箭助力器**,让它可以飞; 张三想:正好我在重新设计代码,不如就拿这个来试试代码,看看能不能达到预期要求; 张三先设计了QuackBehavior接口和FlyBehavior接口的类图  那么Dunk该怎么设计呢?我们可以让Dunk关联于这两个接口,这样就可以让Dunk类使用对应的方法了; 类图:  张三这次留了个心眼,为了能够实现运行时代码的动态拓展,所以加入了set方法,这样就可以随时随地的设置不同鸭子的行为了; 接下来就是实现代码了; **FlyBehavior接口** ``` // 鸭子飞的接口 public interface FlyBehavior { public void fly(); } 1234 ``` 实现FlyBehavior接口: ``` // 用翅膀飞 public class FlyWithWings implements FlyBehavior{ @Override public void fly() { System.out.println("I'm flying!!!"); } } 1234567 ``` ``` // 不能飞 public class FlyNoWay implements FlyBehavior{ @Override public void fly() { System.out.println("I can't flying"); } } 1234567 ``` ``` // 火箭喷射器飞 public class FlyWithRocket implements FlyBehavior { @Override public void fly() { System.out.println("Fly with a rocket!!"); } } 1234567 ``` **QuackBehavior接口** ``` // 鸭子叫的接口 public interface QuackBehavior { public void quack(); } 1234 ``` 实现QuackBehavior接口 ``` // 鸭子嘎嘎叫 public class Quack implements QuackBehavior{ @Override public void quack() { System.out.println("嘎嘎嘎"); } } 1234567 ``` ``` // 橡皮鸭吱吱叫 public class Squeak implements QuackBehavior{ @Override public void quack() { System.out.println("吱吱吱"); } } 1234567 ``` ``` // 不会叫 public class MuteQuack implements QuackBehavior{ @Override public void quack() { System.out.println("我不会叫"); } } 1234567 ``` 下面就是Duck类和具体不同种类的鸭子了 **Dunk类** ``` // 鸭子抽象类 public abstract class Duck { // Duck方法关联于FlyBehavior和QuackBehavior接口 // 这两个接口同样聚合在Dunk类中 private FlyBehavior flyBehavior; // 鸭子飞属性 private QuackBehavior quackBehavior; // 鸭子叫属性 // 抽象方法:显示什么鸭子 public abstract void display(); // 飞行的方法 public void performFly() { flyBehavior.fly(); } // 叫的方法 public void performQuack() { quackBehavior.quack(); } // 设置飞行的方法 public void setFlyBehavior(FlyBehavior flyBehavior) { this.flyBehavior = flyBehavior; } // 设置叫的方法 public void setQuackBehavior(QuackBehavior quackBehavior) { this.quackBehavior = quackBehavior; } } 1234567891011121314151617181920212223242526 ``` ``` // 红头鸭 public class RedheadDuck extends Duck{ public RedheadDuck() { this.setFlyBehavior(new FlyWithWings()); this.setQuackBehavior(new Quack()); } @Override public void display() { System.out.println("我是一只红头鸭!!"); } } 1234567891011 ``` ``` // 绿头鸭 public class MallardDuck extends Duck { // 默认构造方法,目的是给父类两个接口实例化对象 public MallardDuck() { this.setFlyBehavior(new FlyWithWings()); // 用翅膀飞 this.setQuackBehavior(new Quack()); // 嘎嘎叫 } @Override public void display() { System.out.println("我是一只绿头鸭!!"); } } 123456789101112 ``` ``` // 橡皮鸭 public class RubberDuck extends Duck{ public RubberDuck() { this.setFlyBehavior(new FlyNoWay()); // 不能飞 this.setQuackBehavior(new Squeak()); // 吱吱叫 } @Override public void display() { System.out.println("我是一只橡皮鸭!!"); } } 1234567891011 ``` ``` // 模型鸭 public class ModelDuck extends Duck{ public ModelDuck() { this.setFlyBehavior(new FlyWithRocket()); // 火箭喷射器飞 this.setQuackBehavior(new MuteQuack()); // 不会叫 } @Override public void display() { System.out.println("我是一只模型鸭!!"); } } 1234567891011 ``` 终于实现了所有的功能,张三怀着忐忑写了一个测试代码: ``` // 测试系统 public class MiniDuckSimulator { public static void main(String[] args) { Duck mallardDuck = new MallardDuck(); // 绿头鸭 mallardDuck.display(); mallardDuck.performFly(); mallardDuck.performQuack(); System.out.println("-----------------"); Duck redheadDuck = new RedheadDuck(); redheadDuck.display(); redheadDuck.performFly(); redheadDuck.performQuack(); System.out.println("-----------------"); Duck rubberDuck = new RubberDuck(); rubberDuck.display(); rubberDuck.performFly(); rubberDuck.performQuack(); System.out.println("-----------------"); Duck modelDuck = new ModelDuck(); modelDuck.display(); modelDuck.performFly(); modelDuck.performQuack(); modelDuck.setFlyBehavior(new FlyNoWay()); // 动态改变对象行为 modelDuck.performFly(); } } 1234567891011121314151617181920212223242526272829 ``` 输出结果: ``` 我是一只绿头鸭!! I'm flying!!! 嘎嘎嘎 ----------------- 我是一只红头鸭!! I'm flying!!! 嘎嘎嘎 ----------------- 我是一只橡皮鸭!! I can't flying 吱吱吱 ----------------- 我是一只模型鸭!! Fly with a rocket!! 我不会叫 I can't flying 12345678910111213141516 ``` 第二版的代码完美的实现了所有的功能,并且代码的弹性和拓展性都很不错,张三想:升职加薪这不就稳稳地嘛; 李四这时说:这个代码就是用到了设计模式之一 ——**策略模式**,想要升职加薪,光会这一个设计模式可不行,后面的路还长着呢; 体会到了设计模式的好处,张三下定决心好好学习设计模式; ## 总结 上面张三的例子可以看出策略模式的好处 * 不需要许多 if …else或者switch 判断语句 * 代码可拓展性好 * 符合开闭原则,便于维护 同样策略模式需要注意:**每添加一个策略就要增加一个类,当策略过多是会导致策略类膨胀**; --- 其实这个例子中还用到了一个设计原则: **多用组合和聚合,少用泛化(继承)**--- 这里总结一下文中提到的 **三种设计原则**:* 封装变化的行为 * 面向接口编程,不针对实现编程 * 多用组合聚合,少用继承 当然这三个只是这里用到的,对于设计原则可不止这三种,后面会一 一介绍; 再重新看一下策略模式的定义: **策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理。** 策略模式结构图:  其实策略模式在Java源码中也有体现,简单举个例子:**Constructor**就用到了策略模式,我们可以通过实现它来创造不同的排序规则,感兴趣可以看看源码体验一下; 当然一个例子不足以让你会用策略模式,想要真正的掌握还是需要大量的练习和实践,希望这篇文章能给你带来一些启发! 欢迎大家的点评! 最后修改:2024 年 06 月 18 日 © 来自互联网 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏