策略模式-对象行为型模式
定义
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化
类图
- Strategy:策略接口,用来约束一系列具体的算法。Context使用这个接口调用具体的策略实现定义的算法
- ConcreteStrategy:具体的策略实现,也就是具体的算法实现
- Context:上下文环境类,负责具体的策略类交互。维持一个对策略接口的引用,用于定义所采用的策略
策略模式的认识
- 策略的功能就是把具体的算法实现从具体的业务处理中独立出来实现为单独的算法类,从而形成一系列的算法,并让这些算法可以相互替换(策略模式的重心不是如何实现算法,而是如何组织、调用这些算法,从而使程序具有更好的维护性和扩展性)
- 策略模式是把多个平等的具体实现(如 if/else if/else)封装到单独的策略类,然后通过上下文来与具体的策略类进行交互,因此多个if/else语句可以考虑使用策略模式
代码实例
问题描述
假设设计一个模拟鸭子游戏:
游戏中会有各种鸭子,拥有游泳、呱呱叫、飞翔等各种行为,我们可能会这样设计一个鸭子超类,然后让各种鸭子继承此超类
各种鸭子:public abstract class Duck { //抽象外观 public abstract void display(); //行为:游泳 public void swim(){} //行为:呱呱叫 public void quack(){}; //行为:飞翔 public void fly(){}; }
这样似乎没什么问题,每添加一种鸭子就让它继承Duck,但是当新需求要加一个橡皮鸭子的时候,问题出现了,橡皮鸭多了一个本不该出现的行为——飞翔。如果把橡皮鸭的fly()方法覆盖掉怎么样呢?就像这样:class RedHeadDuck extends Duck{ @Override public void display() { System.out.print("红头鸭"); } } class MallardDuck extends Duck{ @Override public void display() { System.out.print("绿头鸭"); } }
但如果新需求又要加入一个不会飞也不会叫的木头鸭子呢?每次有新的鸭子出现,都要被迫检查并可能需要覆盖fly()与quack()方法。。代码将在子类中重复,且改变会牵一发而动全身,造成其他鸭子不想要的改变,维护将成为一个噩梦~class RubberDuck extends Duck{ @Override public void display() { System.out.print("橡皮鸭"); } @Override public void quack() { // 吱吱叫 } @Override public void fly() { //什么也不做 } }
现在我们知道继承是行不通的,因为鸭子的行为在子类中不断的改变,并且让所有子类都有这些行为也是不恰当的。利用接口实现呢?如将fly()从超类取出来单独做成Flyable接口,只有会飞的鸭子才实现这个接口。看着是不错,但要知道Java接口是不具有实现代码的,所以通过接口无法达到代码的复用。此时如果要修改某个行为就必须往下追踪每个定义此行为的类,一不小心就可能出错。
使用策略模式
好了说了这么多,让我们从零开始重新设计,首先封装变化:本例中变化的部分为飞行与鸭子叫的行为,我们单独取出来封装起来,以便以后可以轻易的改动或扩展此部分,而不影响其他不需要变化的部分
我们用接口FlyBehavior 与 QuackBehavior代表飞和叫的行为。从现在开始,鸭子类不再负责实现Flying接口,而是由我们创建一组其他类专门实现FlyBehavior,这就称为行为类。
public interface FlyBehavior { public void fly(); } class FlyWithWings implements FlyBehavior{ @Override public void fly() { System.out.println("用翅膀飞"); } } class FlyNoWay implements FlyBehavior{ @Override public void fly() { System.out.println("不会飞"); } } class FlyWithRocket implements FlyBehavior{ @Override public void fly() { System.out.println("通过火箭动力飞行"); } }
这种做法迥异以往, 以前的做法是:行为来自Duck超类的具体实现,或实现某个接口,这两种做法都依赖于"实现"(具体的实现都绑死在了鸭子子类中)。现在在我们新的设计中,鸭子的子类将使用接口(FlyBehavior 与 QuackBehavior)所表示的行为(通过组合方式,而不是实现),所以实际的"实现"不会被绑死在鸭子的子类中。
这样飞行和呱呱叫的动作就可以被其他的对象复用,因为这些行为已经与鸭子无关了。而我们新增行为就不会影响到既有的行为类,也不会影响到使用了飞行行为的鸭子类/** * 新的鸭子超类(策略模式上下文环境类) */ public abstract class DuckContext { FlyBehavior flyBehavior; QuackBehavior quackBehavior; public abstract void display(); public void swim(){} //将飞行的行为委托给飞行行为接口 public void performFly(){ flyBehavior.fly(); } public void performQuack(){ quackBehavior.quack(); } }
添加一个不会飞、吱吱叫的模型鸭:
class ModelDuck extends DuckContext{ public ModelDuck() { flyBehavior = new FlyNoWay(); quackBehavior = new Squeak(); } @Override public void display() { System.out.println("模型鸭"); } }
运行
public static void main(String[] args) { //创建一个模型鸭 DuckContext duck = new ModelDuck(); duck.performFly(); duck.performQuack(); }
动态设定行为
我们可以通过set方法定义鸭子行为,而不是在鸭子的构造器内实例化
在鸭子超类DuckContext中添加
模型鸭一开始是不能飞的,为模型鸭动态的添加火箭飞行的行为,使起具有飞行能力public void setQuackBehavior(QuackBehavior qb) { this.quackBehavior = qb; } public void setFlyBehavior(FlyBehavior fb){ this.flyBehavior = fb; }
整体类图:public static void main(String[] args) { //创建一个模型鸭 DuckContext duck = new ModelDuck(); duck.performFly(); duck.setFlyBehavior(new FlyWithRocket()); duck.performFly(); }/*output~ 不会飞 通过火箭动力飞行 */
![]()
策略模式总结
策略模式本质:分离算法,选择实现
优点
-
策略模式提供对"开闭原则"的完美支持。用户可以在不修改原系统的基础上选择算法或行为,也可以灵活的增加新的算法或行为,提供更好的扩展性
-
提供了管理相关算法族的方法,策略类的等级结构定义了一个算法或行为族,恰当使用继承可以把公共的代码移到抽象策略类中,从而避免重复的代码
-
避免多重条件语句
缺点
-
如果让客户端来选择具体使用哪一策略发,就需要客户了解每种策略的不同。而且这样也暴露了策略的具体实现
-
增加了类对象数量