22.java设计模式之策略模式
基本需求
- 现有各种鸭子(比如 野鸭、北京鸭、水鸭)
- 鸭子有各种行为(比如 叫、飞行、游泳等)
- 显示鸭子的信息
传统方式
- 定义一个抽象的鸭子父类(其中实现了所有的方法 叫、飞行、游泳等)
- 不同的鸭子继承鸭子的抽象父类,有选择的重写父类中方法、实现不同的鸭子行为不同,如果不重写,就有可能导致错误
- 问题
- 其它鸭子,都继承了Duck类,所以fly让所有子类都会飞了,这是不正确的
- 上面说的问题,其实是继承带来的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分,会有溢出效应
- 为了改进1问题,我们可以通过覆盖 fly 方法来解决 => 覆盖解决 ,违反了里氏替换原则
- 问题又来了,如果我们有一个玩具鸭子ToyDuck, 这样就需要ToyDuck去覆盖Duck的全部方法 => 策略模式解决
基本介绍
-
策略模式(Strategy)中,定义算法族(策略组),分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
-
一个类的行为或其算法可以在运行时更改,这种类型的设计模式属于行为型模式,在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的context对象,策略对象改变context对象的执行算法
-
这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口);第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)
-
UML类图(原理)
-
说明
- StrageyA为策略接口,定义该策略什么行为
- Context类为使用策略的类,聚合了策略接口(可以聚合多个,不同的行为为不同的策略接口)
- ConcreteStrategyA1为StrageyA的一个实现类(该种行为的一个具体策略)
-
UML类图(案例)
-
说明
- FlyBehavior为鸭子飞行的策略接口,有三个实现类
- Duck为鸭子抽象类,有三个子类,聚合了飞行策略接口,提供了fly方法(该方法内部直接调用飞行策略接口中的fly即可)
- Duck聚合飞行策略接口的同时,还可聚合其他的策略接口(比如,游泳策略,叫策略等)
-
代码实现
-
// 飞行策略接口 public interface FlyBehavior { // 飞行方法 void fly(String name); } // 飞行策略实现类一 飞行技术好 class GoodFlyBehavior implements FlyBehavior { @Override public void fly(String name) { System.out.println(name + "飞行技术好"); } } // 飞行策略实现类二 飞行技术一般 class GeneralFlyBehavior implements FlyBehavior { @Override public void fly(String name) { System.out.println(name + "飞行技术一般"); } } // 飞行策略实现类二 不会飞行技术 class NoFlyBehavior implements FlyBehavior { @Override public void fly(String name) { System.out.println(name + "不会飞行技术"); } }
-
// 鸭子抽象类 public abstract class Duck { protected String name; // 聚合飞行策略 可以聚合多种行为策略接口 比如 游泳、叫等 protected FlyBehavior flyBehavior; public Duck(String name, FlyBehavior flyBehavior) { this.name = name; this.flyBehavior = flyBehavior; } public void setFlyBehavior(FlyBehavior flyBehavior) { this.flyBehavior = flyBehavior; } // 飞行方法 public void fly() { // 判断策略,调用飞行策略接口中的飞行策略 if (null != flyBehavior) { this.flyBehavior.fly(this.name); } } } // 子类一 野鸭 飞行策略采用好 class WildDuck extends Duck { public WildDuck(String name, FlyBehavior flyBehavior) { super(name, flyBehavior); } } // 子类二 北京鸭 飞行策略采用一般 class PekingDuck extends Duck { public PekingDuck(String name, FlyBehavior flyBehavior) { super(name, flyBehavior); } } // 子类三 玩具鸭 飞行策略采用不能飞 class ToyDuck extends Duck { public ToyDuck(String name, FlyBehavior flyBehavior) { super(name, flyBehavior); } }
-
public class Client { public static void main(String[] args) { // 创建野鸭 飞行技术好 WildDuck wildDuck = new WildDuck("野鸭", new GoodFlyBehavior()); wildDuck.fly(); // 创建北京鸭 飞行技术一般 PekingDuck pekingDuck = new PekingDuck("北京鸭", new GeneralFlyBehavior()); pekingDuck.fly(); // 创建玩具鸭 不会飞行 ToyDuck toyDuck = new ToyDuck("玩具鸭", new NoFlyBehavior()); toyDuck.fly(); } }
-
jdk源码
- 在jdk中Arrays的Comparator就使用了策略模式(自定义排序规则)
- 使用lambda表达式函数式接口时也是策略接口的使用
注意事项
- 策略模式的关键是:分析项目中变化部分与不变部分
- 策略模式的核心思想是:多用组合/聚合少用继承;用行为类组合,而不是行为的继承,更有弹性
- 体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可,避免了使用多重转移语句(if..else if..else)
- 提供了可以替换继承关系的办法: 策略模式将算法封装在独立的 Strategy 类中使得你可以独立于其 Context 改变它,使它易于切换、易于理解、易于扩展
- 需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大