Java 设计模式--策略(Strategy)

来源:互联网 发布:js跨域获取网页源代码 编辑:程序博客网 时间:2024/04/29 07:33

前期需求:设计一组鸭子,鸭子形态各异,能游泳戏水,能呱呱叫。
需求分析:因为所有的鸭子都会游泳,都会呱呱叫,所以我们可以设计一个超类,让它负责实现这部分行为,其他鸭子继承超类就具备了这些行为;因为鸭子形态各异,所以具有一个抽象方法,让子类去负责实现外观行为,这样每个鸭子的外观都不相同。

package com.example.administrator.duck;/** * 文 件 名: Duck * 创 建 人: Bradley * 创建日期: 2017/10/14 10:19 * 修改时间: * 修改备注: */public abstract class Duck {    protected  void quck(){        System.out.println("quck");    }    protected  void swim(){        System.out.println("swim");    }    /**     * 因为每一种鸭子的外观都不同,所以 display 方法是抽象的     */    public  abstract void display();}

中期需求更改:想让鸭子具有飞的行为。
既然鸭子想有飞的行为,可以在 Duck 类中添加 fly() 方法,这样所有其他鸭子继承 Duck 就具备了飞的行为。

package com.example.administrator.duck;/** * 文 件 名: Duck * 创 建 人: Bradley * 创建日期: 2017/10/14 10:19 * 修改时间: * 修改备注: */public abstract class Duck {    protected  void quck(){        System.out.println("quck");    }    protected  void swim(){        System.out.println("swim");    }    /**     * 所以的子类都会继承 fly ,就具备了飞的行为     */    protected  void fly(){        System.out.println("fly");    }    /**     * 因为每一种鸭子的外观都不同,所以 display 方法是抽象的     */    public  abstract void display();}

但在效果演示时,出现了木头鸭会飞的现象。为什么?因为木头鸭继承了 Duck,也具备了飞的行为了。在超类中加入 fly(),就会导致所有子类都具备 fly(),连那些不具备 fly() 的子类也无法免除,使得某些并不适合该行为的子类也具有该行为。
解决策略:
1. 让所有子类都覆写 fly 方法,就如同覆写 display 方法一样。但一旦有新的鸭子出现,就要不断的检查是否要覆写这两个方法。
2. 使用接口,将 fly 从超类中取出来,放进一个 Flyable 抽象接口中。这样一来,只有会飞的鸭子才实现此接口,同样的方法,也可以用来设计一个 Quackable 接口,因为不是所有的鸭子都会叫。但代码无法复用,所有的鸭子都要覆写这两个方法。

重新设计鸭子的行为
因为 Duck 类中的 fly 和 quack 会随着鸭子的不同而改变,为了把这两个行为从 Duck 类中分开,我们将把他们从 Duck 类中取出来,建立一组新类来代表每个行为,此类专门提供某行为的接口实现。
我们利用接口代表每个行为,比方说,FlyBehavior 和 QuackBehavior ,而行为的每个实现都实现其中的一个接口。
所以这次鸭子类不会负责实现 FlyBehavior 和 QuackBehavior 接口,反而是由我们制造一组其他类专门实现 FlyBehavior 和 QuackBehavior 接口,这就称为 “行为” 类,由行为类而不是 Duck 类来实现行为接口。
这样的做法迥异于以往,以前的做法是:行为来自 Duck 超类的具体实现,或是继承某个接口并由子类自行实现而来。
在我们新的设计中,鸭子的子类将使用接口所表示的行为,所以实际的实现不会被绑死在鸭子的子类中。

飞行接口及其子类

/** * 所有飞行行为类必须实现的接口 */public interface FlyBehavior {    public void fly();}/** * 这是飞行行为的实现,给真正会飞的鸭子用 */public class FlyWithWings implements FlyBehavior {    @Override    public void fly() {        System.err.println("FlyWithWings");    }}/** * 这是飞行行为的实现,给不会飞的鸭子用 */public class FlyNoWay implements FlyBehavior {    @Override    public void fly() {        System.err.println("FlyNoWay");    }}

呱呱叫接口及其子类

public interface QuackBehavior {    public void quack();}public class Quack implements QuackBehavior {    @Override    public void quack() {        System.err.println("Quack");    }}public class MuteQuack implements QuackBehavior {    @Override    public void quack() {        System.err.println("MuteQuack");    }}public class Squeak implements QuackBehavior {    @Override    public void quack() {        System.err.println("Squeak");    }}

定义鸭子的超类

public abstract class Duck {    /**     * 为行为接口类型声明两个引用变量,所有鸭子子类都继承他们     */    FlyBehavior mFlyBehavior;    QuackBehavior mQuackBehavior;    /**     * 通过这这两个 set 方法可以动态改变鸭子的行为     * @param flyBehavior     */    public void setFlyBehavior(FlyBehavior flyBehavior){        mFlyBehavior = flyBehavior;    }    public void setQuackBehavior(QuackBehavior quackBehavior){        mQuackBehavior = quackBehavior;    }    /**     * 委托给行为类     */    protected  void performQuck(){        mQuackBehavior.quack();    }    protected  void performFly(){        mFlyBehavior.fly();    }    protected  void swim(){        System.out.println("swim");    }    /**     * 因为每一种鸭子的外观都不同,所以 display 方法是抽象的     */    public  abstract void display();}

建立一个木头鸭

/** * MallardDuck 继承 Duck ,所以具有 mFlyBehavior mQuackBehavior 的实例变量 * 绿头鸭使用 Quack 来处理呱呱叫,所以当执行 performQuck 时,叫的则指被委托给 Quack 对象 */public class MallardDuck extends Duck {    public  MallardDuck(){        mFlyBehavior = new FlyWithWings();        mQuackBehavior = new Quack();    }    @Override    public void display() {    }}

编写测试类,并动态改变鸭子的行为

public class MiniDuckSimulator {    public static void main(String[] args) {        Duck mallard = new MallardDuck();        mallard.performFly();        mallard.performQuck();        /**         * 在运行时改变鸭子的行为,使鸭子不具备飞行的能力         */        mallard.setFlyBehavior(new FlyNoWay());        mallard.performFly();    }}

策略模式定义
策略模式属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。

策略模式的结构
  策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理。策略模式通常把一个系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是:“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。下面就以一个示意性的实现讲解策略模式实例的结构。
这里写图片描述

这个模式涉及到三个角色:
* 环境(Context)角色:持有一个Strategy的引用。
* 抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
* 具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。

  
认识策略模式
1. 策略模式的重心不是如何实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。
2. 策略模式一个很大的特点就是各个策略算法的平等性。对于一系列具体的策略算法,大家的地位是完全一样的,正因为这个平等性,才能实现算法之间可以相互替换。所有的策略算法在实现上也是相互独立的,相互之间是没有依赖的。所以可以这样描述这一系列策略算法:策略算法是相同行为的不同实现。
3. 运行期间,策略模式在每一个时刻只能使用一个具体的策略实现对象,虽然可以动态地在不同的策略实现中切换,但是同时只能使用一个。
4. 经常见到的是,所有的具体策略类都有一些公有的行为。这时候,就应当把这些公有的行为放到共同的抽象策略角色Strategy类里面。当然这时候抽象策略角色必须要用Java抽象类实现,而不能使用接口。这其实也是典型的将代码向继承等级结构的上方集中的标准做法。

策略模式的优点
1. 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免代码重复。
2. 使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后。

策略模式的缺点
1. 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道算法或行为的情况。
2. 由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。