设计模式中的多态——策略模式详解

1. 关于策略模式

策略模式和java语言的多态特性有些像。java的多态特性容许咱们面向接口编程,不用关心接口的具体实现。接口所指向的实现类,以及经过接口调用的方法的具体行为能够到运行时才绑定。这么作最大的好处是在尽量实现代码复用的前提下更好地应对具体实现类的变化。好比我想增长一种接口的实现或者修改原有实现类的某个行为,那我几乎不用修改任何客户端代码。策略模式能够说正是这种思想在设计模式上的运用。它可使咱们更好的复用代码,同时使程序结构设计更有弹性,更好的应对变化。算法

2. 策略模式详解

2.1 策略模式定义

策略模式定义了一系列算法,并将每个算法封装起来,并且使它们还能够相互替换。策略模式让算法独立于使用它的客户端而独立的变化。

可使用多态进行类比来理解策略模式的定义。一系列算法能够理解成接口的不一样实现类,由于不一样实现类都实现了相同的接口,于是它们也能够相互替换。策略模式让算法独立于客户端而变化与接口的实现类能够独立于使用接口的客户端变化相似。编程

2.2 策略模式的UML类图

从UML类图上能够看出,策略模式中主要有3个角色设计模式

  • 抽象策略接口
    上图中的Strategy即抽象策略接口,接口中定义了抽象的策略算法algorithm()。ide

  • 具体的策略实现类
    上图中的StrategyA和StrategyB即具体的策略实现。不一样的策略实现类都实现了抽象策略接口,并重写了其抽象策略方法。由于都实现了相同的策略接口,于是算法能够相互替换,而且能够动态的改变具体的算法实现。优化

  • 封装策略的上下文环境
    上图中的Context即策略的上下文环境。它屏蔽了高层模块对策略算法的直接访问,封装了可能存在的变化。并且提供了修改Strategy的setter方法,能够动态的改变算法的具体实现。this

3.策略模式的优势

咱们能够结合使用策略模式的例子并与其它实现方案进行对比来看看策略模式到底有什么好处编码

3.1 一个使用策略模式的例子

定义一个汽车类Car。因为汽车最大的特色是能跑,于是咱们赋予该类一个move行为。但要跑起来须要提供能源,一般而言这种能源是汽油,但如今纯靠电池驱动的汽车也愈来愈多。于是Car的move行为就有两种不一样的行为,一种是使用汽油跑,一种是使用电能跑。于是咱们能够这么定义设计

  • 抽象的汽车类Car
/**
 * @author: takumiCX
 * @create: 2018-10-13
 **/
public abstract class Car {

    //汽车品牌
    private String brand;

    public Car(String brand) {
        this.brand = brand;
    }

    public Car(String brand, MoveStrategy strategy) {
        this.brand = brand;
        this.moveStrategy=strategy;
    }

    //汽车的运行策略:使用汽油运行,使用电能运行等等
    private MoveStrategy moveStrategy;

    //运行方法
    public void move() {
        System.out.print(brand);
        moveStrategy.move();
    }

    public void setMoveStrategy(MoveStrategy moveStrategy) {
        this.moveStrategy = moveStrategy;
    }
}

在抽象汽车类中定义了一个move()方法表示汽车具备运行的行为,可是因为究竟是使用汽油运行仍是使用电能运行并无直接定义在里面,而是调用了策略接口中定义的move方法。该策略接口以组合的方式封装在Car内部,并提供了setter方法供客户端动态切换汽车的运行方式。code

  • 使用汽油运行的策略实现
/**
 * @author: takumiCX
 * @create: 2018-10-14
 **/

/**
 * 使用汽油运行的策略实现
 */
public class GasolineMoveStrategy implements MoveStrategy{

    @Override
    public void move() {
        System.out.println(" Use Gasoline Move!");
    }
}
  • 使用电池运行的策略实现
/**
 * @author: takumiCX
 * @create: 2018-10-15
 **/

/**
 * 使用电能运行的策略实现
 */
public class ElectricityMoveStrategy implements MoveStrategy {
    @Override
    public void move() {
        System.out.println(" Use Electricity Move!");
    }
}
  • 具体的汽车实现类
    好比咱们经过继承的方式定义一辆特斯拉汽车,特斯拉汽车默认是纯电动的
/**
 * @author: takumiCX
 * @create: 2018-10-13
 **/
public class TeslaCar extends Car {

    public TeslaCar(String brand) {
        super(brand,new ElectricityMoveStrategy());
    }
}
  • 客户端代码
    首先构造一辆特斯拉车观察其运行方式,并经过setter方法动态改变其运行方式为汽油驱动
/**
 * @author: takumiCX
 * @create: 2018-10-13
 **/
public class Client {

    public static void main(String[] args) {

        TeslaCar car = new TeslaCar("Tesla");

        car.move();

        car.setMoveStrategy(new GasolineMoveStrategy());

        car.move();
    }
}
  • 运行结果

3.2 与其余实现方式的对比

其实上面的例子除了使用策略模式外,还有其余实现方式,但它们都有比较明显的缺点。

3.2.1接口的实现方式

/**
 * @author: takumiCX
 * @create: 2018-10-15
 **/
public interface Move {
    
    void move();
}

并让抽象父类Car实现它

/**
 * @author: takumiCX
 * @create: 2018-10-13
 **/
public abstract class Car implements Move{
    //汽车品牌
    private String brand;

    public Car(String brand) {
        this.brand = brand;
    }
}

这样全部继承Car的具体汽车类都必须实现本身的move方法,也就是让具体的汽车子类来决定汽车的具体行为:究竟是使用汽油运行仍是使用电池运行。可是这么作至少有如下几个缺点

  • 1.具体的汽车运行行为不方便后期维护。于是move行为没法被复用,具体的实现都分散在了子类中。若是要对某种驱动方式的实现进行修改,不得不修改全部子类,这简直是灾难。

  • 2.致使类数量的膨胀。一样品牌的汽车,因为有汽油和电动两种运行方式,不得不为其维护两个类,若是在增长一种驱动方式,好比氢能源驱动,那不得为每一个品牌的汽车再增长一个类。

  • 3.不方便move行为的扩展,也不方便动态的更换其实现方式。

3.2.2 if-else的实现方式

move方法接受客户端传递的参数,经过if-else或者swich-case进行判断,选择正确的驱动方式。

public void move(String moveStrategy) {
    if("electricity".equals(moveStrategy)){
        System.out.println(" Use Electricity Move!");
    }else if("gasoline".equals(moveStrategy)){
        System.out.println(" Use Gasoline Move!");
    }
}

但这样作至关于硬编码,不符合开闭原则。好比我要增长一种氢能源的驱动方式,这种实现就须要修改move中的代码。而若是使用上面说的策略模式,则只须要增长一个实现实现策略接口的具体策略实现类,而不须要修改move中的任何代码,便可被客户端所使用。

/**
 * @author: takumiCX
 * @create: 2018-10-15
 **/
public class HydrogenMovetrategy implements MoveStrategy {
    @Override
    public void move() {
        System.out.println(" Use Hydrogen Move!");
    }
}

3.3 使用策略模式的优势

  • 1.能够优化类结构,当类的某种功能有多种实现时,能够在类中定义策略接口,将真正的功能实现委托给具体的策略实现类。这样避免了类膨胀,也能更好的进行扩展和维护。

  • 2.避免使用多重条件判断致使的硬编码和扩展性差的问题

  • 3.可使具体的算法实现自由切换,加强程序设计的弹性。

4. 使用工厂方法模式改进原有策略模式

全部的策略实现都须要对外暴露,上层模块必须知道具体的策略实现类,这与迪米特法则相违背。为此,可使用工厂方法模式进行解耦。

  • 策略工厂接口
/**
 * @author: takumiCX
 * @create: 2018-10-16
 **/
public interface MoveStrategyFactory {

    MoveStrategy create();
}
  • 氢能源驱动方式的工厂
/**
 * @author: takumiCX
 * @create: 2018-10-16
 **/
public class HydrogenMoveStrategyFactory implements MoveStrategyFactory {
    @Override
    public MoveStrategy create() {
        return new HydrogenMovetrategy();
    }
}
  • 客户端
/**
 * @author: takumiCX
 * @create: 2018-10-13
 **/
public class Client {

    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {

        TeslaCar car = new TeslaCar("Tesla");

        MoveStrategyFactory factory = new HydrogenMoveStrategyFactory();

        MoveStrategy moveStrategy = factory.create();

        car.setMoveStrategy(moveStrategy);

        car.move();

    }
}

这样咱们经过工厂方法模式封装了具体策略类的建立过程,同时也避免了向高层模块暴露。最后运行结构以下

5. 总结

当完成某项功能有多种不一样的实现时,能够实用策略模式。策略模式封装了不一样的算法,而且使这些算法能够相互替换,这提升了代码的复用率也加强了程序设计的弹性。而且能够结合其余设计模式好比工厂方法模式向上层模块屏蔽具体的策略类,使代码更易于扩展和维护。

5. 参考资料

  • 《Head First 设计模式》
  • 《设计模式之禅》
相关文章
相关标签/搜索