设计模式 - 策略模式

在理解策略模式以前咱们假设有这样一个需求场景:咱们在写订单支付场景的代码时,客户能够选择多种支付方式,有银联支付、支付宝支付、微信支付、京东白条等等。而后咱们就极可能就会编写出相似下面这样的代码:html

/**
 * 订单类,拥有一个支付方法
 *
 * @author eamon.zhang
 * @date 2019-11-06 上午9:18
 */
public class Order {
    // 订单id
    private String orderId;
    // 支付方式
    private String payType;
    // 订单金额
    private long amount;

    public Order(String orderId, String payType, long amount) {
        this.orderId = orderId;
        this.payType = payType;
        this.amount = amount;
    }

    /**
     * 订单支付方法
     *
     * @return
     */
    public boolean pay() {
        // 是否支付成功
        boolean payment = false;
        if ("aliPay".equals(payType)) {
            System.out.println("用户选择 支付宝 支付,订单号为:" + orderId + " ,支付金额:" + amount);
            payment = true;
        } else if ("unionPay".equals(payType)) {
            System.out.println("用户选择 银联 支付,订单号为:" + orderId + " ,支付金额:" + amount);
            payment = true;
        } else if ("jdPay".equals(payType)) {
            System.out.println("用户选择 京东 支付,订单号为:" + orderId + " ,支付金额:" + amount);
            payment = true;
        } else if ("wechatPay".equals(payType)) {
            System.out.println("用户选择 微信 支付,订单号为:" + orderId + " ,支付金额:" + amount);
            payment = true;
        }

        return payment;
    }

}

客户端:前端

@Test
public void test() {
    String orderId = "123";
    String payType = "aliPay";
    long amount = 200;
    // 建立订单
    Order order = new Order(orderId, payType, amount);
    // 支付
    order.pay();
}

结果:java

用户选择 支付宝 支付,订单号为:123 ,支付金额:200

能够看出这段代码在逻辑上没有问题,也可以很好的运行;git

可是存在一个问题:将全部的支付方式都写在同一个方法里面,显得有点臃肿,还带来了一个扩展的问题,若是咱们再增长一种支付方式,咱们就不得不修改这段代码,再增长一条 if...else,这就下降了代码的可维护性。违背了开闭原则算法

那可否有一种方法能让咱们既不修改主要逻辑代码的前提下让代码变得更优雅也能很好的对其进行扩展呢?那不防咱们一块儿来看看今天的主角:策略模式后端

定义

策略模式属于对象的行为模式。其用意是针对一组算法,将每个算法封装到具备共同接口的独立的类中,从而使得它们能够相互替换。策略模式使得算法能够在不影响到客户端的状况下发生变化,也就是在策略模式(Strategy Pattern)中,一个类的行为或其算法能够在运行时更改。设计模式

结构

策略模式中通常会涉及到三个角色:安全

  • 策略接口角色 IStrategy:用来约束一系列具体的策略算法,策略上下文角色 ConcreteStrategy 使用此策略接口来调用具体的策略所实现的算法。
  • 具体策略实现角色 ConcreteStrategy:具体的策略实现,即具体的算法实现。
  • 策略上下文角色 StrategyContext:策略上下文,负责和具体的策略实现交互,一般策略上下文对象会持有一个真正的策略实现对象,策略上下文还可让具体的策略实现从其中获取相关数据,回调策略上下文对象的方法。

类图结构:

简单代码实现

先建立抽象策略接口 IStrategy微信

/**
 * 策略类抽象接口,具体策略实现由其子类来实现
 *
 * @author EamonZzz
 * @date 2019-11-02 16:12
 */
public interface IStrategy {

    /**
     * 定义的抽象算法方法 来约束具体的算法实现方法
     */
    void algorithmMethod();
}

建立具体的策略实现类 ConcreteStrategyA前后端分离

/**
 * 策略具体实现类A
 *
 * @author EamonZzz
 * @date 2019-11-02 16:48
 */
public class ConcreteStrategyA implements IStrategy {

    /**
     * 具体的算法体现
     */
    @Override
    public void algorithmMethod() {
        System.out.println("this is ConcreteStrategyA method...");
    }
}

建立具体的策略实现类 ConcreteStrategyB:

/**
 * 策略具体实现类B
 *
 * @author EamonZzz
 * @date 2019-11-02 16:48
 */
public class ConcreteStrategyB implements IStrategy {

    /**
     * 具体的算法体现
     */
    @Override
    public void algorithmMethod() {
        System.out.println("this is ConcreteStrategyB method...");
    }
}

建立具体的策略实现类 ConcreteStrategyC:

/**
 * 策略具体实现类C
 *
 * @author EamonZzz
 * @date 2019-11-02 16:48
 */
public class ConcreteStrategyC implements IStrategy {

    /**
     * 具体的算法体现
     */
    @Override
    public void algorithmMethod() {
        System.out.println("this is ConcreteStrategyC method...");
    }
}

建立策略上下文 StrategyContext

/**
 * 策策略上下文,负责和具体的策略实现交互,一般策略上下文对象会持有一个真正的策略实现对象,
 * 策略上下文还可让具体的策略实现从其中获取相关数据,回调策略上下文对象的方法。
 *
 * @author EamonZzz
 * @date 2019-11-02 16:11
 */
public class StrategyContext {
    /**
     * 策略实现的引用
     */
    private IStrategy strategy;

    /**
     * 构造器注入具体的策略类
     *
     * @param iStrategy 策略实现的引用
     */
    public StrategyContext(IStrategy iStrategy) {
        this.strategy = iStrategy;
    }

    public void contextMethod() {
        // 调用策略实现的方法
        strategy.algorithmMethod();
    }
}

最后编写测试类来测试一下结果

/**
 * @author EamonZzz
 * @date 2019-11-02 16:53
 */
public class StrategyContextTest {

    @Test
    public void test(){
        // 1. 建立具体的策略实现
        IStrategy strategy = new ConcreteStrategyA();
        // 2. 在建立策略上下文的同时,将具体的策略实现对象注入到策略上下文当中
        StrategyContext ctx = new StrategyContext(strategy);
        // 3. 调用上下文对象的方法来完成对具体策略实现的回调
        ctx.contextMethod();
    }

}

控制台输出:

this is ConcreteStrategyA method...

改造

在简单的了解了策略模式以后,再看看文章开头的实例场景,咱们使用策略模式来对其进行改造:

咱们将订单支付逻辑中的各类支付场景算法单独抽离出来:

先建立抽象的支付接口 Payment ,让各类平台的支付逻辑类都实现该接口,达到统一调用的目的:

/**
 * 统一支付接口
 *
 * @author eamon.zhang
 * @date 2019-11-06 上午9:44
 */
public interface Payment {
    boolean pay(String orderId, long amount);
}

而后分别建立支付宝支付类 AliPay

/**
 * 支付宝支付
 *
 * @author eamon.zhang
 * @date 2019-11-06 上午9:48
 */
public class AliPay implements Payment {
    @Override
    public boolean pay(String orderId, long amount) {
        System.out.println("用户选择 支付宝 支付,订单号为:" + orderId + " ,支付金额:" + amount);
        return true;
    }
}

建立微信支付类 WeChatPay

/**
 * 微信支付
 *
 * @author eamon.zhang
 * @date 2019-11-06 上午9:49
 */
public class WeChatPay implements Payment {
    @Override
    public boolean pay(String orderId, long amount) {
        System.out.println("用户选择 微信 支付,订单号为:" + orderId + " ,支付金额:" + amount);
        return true;
    }
}

建立银联支付类 UnionPay

/**
 * 银联支付
 *
 * @author eamon.zhang
 * @date 2019-11-06 上午9:50
 */
public class UnionPay implements Payment {
    @Override
    public boolean pay(String orderId, long amount) {
        System.out.println("用户选择 银联 支付,订单号为:" + orderId + " ,支付金额:" + amount);
        return true;
    }
}

而后建立订单类 Order :

/**
 * 订单类,至关于 策略上下文角色
 *
 * @author eamon.zhang
 * @date 2019-11-06 上午9:43
 */
public class Order {
    // 订单id
    private String orderId;
    // 金额
    private long amount;
    // 具体支付类型的引用
    private Payment payType;

    public Order(String orderId, Payment payType, long amount) {
        this.orderId = orderId;
        this.payType = payType;
        this.amount = amount;
    }

    /**
     * 订单支付方法
     *
     * @return
     */
    public boolean pay() {
        boolean paySuccess;
        // 调用支付接口
        paySuccess = payType.pay(orderId, amount);

        if (!paySuccess) {
            // 支付失败逻辑
            System.out.println("支付失败!");
        }
        return paySuccess;
    }
}

最后建立咱们的客户端模拟调用:

@Test
public void test() {
    // 建立支付策略
    Payment weChatPay = new WeChatPay();

    // 建立策略上下文(订单),并将具体的策略实现注入
    String orderId = "123456";
    long amount = 150;
    Order order = new Order(orderId, weChatPay, amount);

    // 调用具体支付策略逻辑
    order.pay();
}

运行结果:

用户选择 微信 支付,订单号为:123456 ,支付金额:150

这样就对订单支付场景完成了一个基本的改造,订单支付的选择权直接在用户选择支付方式时建立,订单支付方法中统一进行调用;当咱们须要新增长一种支付方式时,就能够直接继承 Payment 抽象支付策略接口,而后实现支付方法,好比咱们如今增长了一种京东白条支付 JdPay 就能够这样写:

/**
 * 京东支付
 *
 * @author eamon.zhang
 * @date 2019-11-06 上午9:49
 */
public class JdPay implements Payment {
    @Override
    public boolean pay(String orderId, long amount) {
        System.out.println("用户选择 京东 支付,订单号为:" + orderId + " ,支付金额:" + amount);
        return true;
    }
}

一样的在客户端调用:

/**
 * @author eamon.zhang
 * @date 2019-11-06 上午10:00
 */
public class OrderTest {

    @Test
    public void test() {
        // 建立支付策略
        Payment jdPay = new JdPay();

        // 建立策略上下文(订单),并将具体的策略实现注入
        String orderId = "123456";
        long amount = 150;
        Order order = new Order(orderId, jdPay, amount);

        // 调用具体支付策略逻辑
        order.pay();
    }

}

运行结果:

用户选择 京东 支付,订单号为:123456 ,支付金额:150

能够看到,在通过使用 策略模式 改造以后,咱们的订单支付的扩展变得很是的容易,增长支付方式时,直接建立一个类并实现支付逻辑便可,不须要再修改咱们的主类 Order。这就遵循了 开闭原则

再改造

上面第一次改造,只能勉强说明 策略模式 给实际业务带来的好处,可是回到咱们现实的支付场景中,用户选择支付方式而且支付的操做都是在前端页面进行的,并且如今大都使用 先后端分离 的模式来进行开发,并不能像 JSP 那样,能够在页面中建立 Java 对象,在先后端分离的场景中,全部参数都是从页面构建好键值对传入,其基本类型为数字、字符串等等。因此咱们能够再结合以前说的 工厂模式 进行改造,使其更适合现实场景。

建立支付策略的工厂类 PayStrategyFactory

/**
 * 建立支付策略的工厂类
 *
 * @author eamon.zhang
 * @date 2019-11-06 上午10:32
 */
public class PayStrategyFactory {

    // 支付方式常量
    public static final String ALI_PAY = "aliPay";
    public static final String JD_PAY = "jdPay";
    public static final String WECHAT_PAY = "wechatPay";
    public static final String UNION_PAY = "unionPay";

    // 支付方式管理集合
    private static Map<String, Payment> PAYMENT_STRATEGY_MAP = new HashMap<>();

    static {
        PAYMENT_STRATEGY_MAP.put(ALI_PAY, new AliPay());
        PAYMENT_STRATEGY_MAP.put(JD_PAY, new JdPay());
        PAYMENT_STRATEGY_MAP.put(WECHAT_PAY, new WeChatPay());
        PAYMENT_STRATEGY_MAP.put(UNION_PAY, new UnionPay());
    }

    /**
     * 获取支付方式类
     *
     * @param payType 前端传入支付方式
     * @return
     */
    public static Payment getPayment(String payType) {
        Payment payment = PAYMENT_STRATEGY_MAP.get(payType);
        if (payment == null) {
            throw new NullPointerException("支付方式选择错误!");
        }
        return payment;
    }
}

而后结合实际状况对 Order 类进行修改,使其支付方式的选择权交由用户作支付动做时进行选择,由于提交订单后能够选择不支付,这时候订单能够先建立:

/**
 * 订单类,至关于 策略上下文角色
 *
 * @author eamon.zhang
 * @date 2019-11-06 上午9:43
 */
public class Order {
    // 订单id
    private String orderId;
    // 金额
    private long amount;


    public Order(String orderId, long amount) {
        this.orderId = orderId;
        this.amount = amount;
    }

    /**
     * 订单支付方法
     *
     * @return
     */
    public boolean pay(String payType) {
        boolean paySuccess;
        Payment payment = PayStrategyFactory.getPayment(payType);
        // 调用支付接口
        paySuccess = payment.pay(orderId, amount);

        if (!paySuccess) {
            // 支付失败逻辑
            System.out.println("支付失败!");
        }
        return paySuccess;
    }
}

最后建立测试代码:

@Test
public void test() {
    // 前端传入的参数
    String orderId = "01000005";
    String payType = PayStrategyFactory.ALI_PAY;
    long amount = 190;

    // 建立策略上下文(订单),并将具体的策略实现注入
    Order order = new Order(orderId, amount);
    // 实际状况是 在支付的时候选择支付方式,由于有可能先提交了订单,后面再付款
    order.pay(payType);
}

测试结果:

用户选择 支付宝 支付,订单号为:01000005 ,支付金额:190

这样才算完成了一个比较友好且更贴合实际业务状况的业务代码。固然这只是简单的一个示例,现实中,代码的逻辑会很是复杂;现实中各类设计模式一般咱们会配合进行使用,策略模式的使用频率也很是的高,但愿你们看完以后可以理解并运用。

总结

应用场景

  1. 假如系统中有不少类,而他们的区别仅仅只是他们之间的行为,那么使用策略模式能够动态地让一个对象在许多行为中选择一种行为
  2. 一个系统须要动态地在几种算法中选择一种
  3. 若是一个对象有不少的行为,若是不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现

经常使用来解决的问题

在有多种算法类似的状况下,解决使用 if...else 所带来的复杂和难以维护的问题

在JDK中的体现

在 JDK 源码中也有很是多的 策略模式 的运用,比较经常使用的就是 Comparator 接口,它有很是多的实现方法,源码中也有不少地方对其进行引用

若是有兴趣,可使用工具跟进查看一下,这里就不作过多的分析了

优缺点

优势

  1. 策略模式符合开闭原则
  2. 避免了代码中过多的 if...else 和switch 语句的出现
  3. 使用策略模式能够提升算法的保密性和安全性

缺点

  1. 客户端必须知道素有的策略,并决定使用哪种
  2. 代码中会出现比较多的策略类,增长维护难度

源码

本文源码:https://git.io/JeaYZ

欢迎star


推荐阅读和参考资料:

https://www.cnblogs.com/java-my-life/archive/2012/05/10/2491891.html

https://www.cnblogs.com/lewis0077/p/5133812.html

相关文章
相关标签/搜索