设计模式之策略模式

设计模式,无论是coder们业余聊天,还是面试时面试官喜欢出的问题中,都会看到它的影子。设计模式,是基于面向对象之上的,应用好设计模式,我们在平时开发,还是架构设计,在系统的架构性,可拓展,可维护性方面的考虑都会有质的提升。当我们会一些基础语法,逻辑控制之后,就需要考虑我现在写的代码,在以后的拓展,维护会有那些瓶颈,如何改善,这些问题考虑清楚后在进行编码,开发就会事半功倍。范围说小一点,基于Java , 无论是JDK源码,还是我们每天都在接触的各种框架,都使用了各种设计模式。

设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

本文会先着重写一下策略模式的应用。后期考虑会写几篇博文来记录学习其他设计模式。

PS: 本人是在边学编写,如果有错误,望指正。

策略模式(Strategy Pattern)

在策略模式中,一个行为可以在系统运行时动态修改。这种类型设计模式是行为型设计模式,策略模式中,会有代表各种策略的对象和执行策略的context上下文对象。策略对象改变context执行的算法。

举例:

  1. 出门旅行,拥有很多出行策略,比如飞机,汽车,火车等;
  2. JDK AWT中的LayoutManager;
  3. 支付系统中,有很多支付策略,比如支付宝,微信,银联。在支付时需要选择其中一种支付策略进行支付业务。

简要介绍

  1. 用途:定义一系列策略,他们实现自己的算法,使用策略模式实现在运行时动态替换各个策略。
  2. 解决难点:在有很多类似算法的实现中,使用 if … else 会造成难以维护和拓展。
  3. 使用场景:在一个系统中有很多类,它们之间唯一的区别只在与行为的不同,而且需要系统需要动态选择这些行为中的一种,如果不使用适当的设计模式,就会产生多重条件选择语句。

设计

需求说明:系统需要添加支付功能模块,且支付渠道提供支付宝,微信,银联三种,通过各自支付方式进行支付。

上述的三种支付渠道,当用户选择一种后,到达后台进行业务处理,会有三个条件选择,于是伪代码如下:

if(支付宝){
  支付宝.pay();
  //...
}else if(微信){
  微信.pay();
  //...
}else if(银联){
  银联.pay();
  //...
}else{
  未知支付方式...
}

  

感觉没啥问题。

但是假如项目上线了,公司业务壮大,想要引入第四种支付渠道,比如是一个第三方渠道商,就叫支付宝二号,现在需要拓展时,就需要找到这个项目的源代码,更改IF语句后重新编译项目,打包运行。这样就会多出来一堆的维护时间,而且如果期间改错代码,也会浪费很多时间。

那么,如何使用策略模式实现这种运行时动态选择行为策略,而且后期容易拓展的支付系统呢?

首先设计类图,如下图

实现

  1. 抽象类,既然需要动态更改行为,多态是必须的。

    public interface IPayStrategy{
          public void pay();//支付
    }
  2. 各个行为对象

    微信支付

    public class WexinPayStrategy implements IPayStrategy {
        @Override
        public void pay() {
            System.out.println("微信支付.");
        }
    }

    支付宝支付

    public class AliPayStrategy implements IPayStrategy{
        @Override
        public void pay() {
            System.out.println("支付宝支付.");
        }
    }

    银联支付

    public class EBankPayStrategy implements IPayStrategy{
        @Override
        public void pay() {
            System.out.println("银联支付.");
        }
    }

    负责调度,执行入口的上下文 context

    public class PayStrategyContext {
        private IPayStrategy payStrategy;
        public PayStrategyContext() {
        }
        /**
         * 执行支付
         */
        public void excutePay() {
            if (null == payStrategy) {
                throw new RuntimeException("支付策略未配置");
            }
            payStrategy.pay();
        }
        public IPayStrategy getPayStrategy() {
            return payStrategy;
        }
        public void setPayStrategy(IPayStrategy payStrategy) {
            this.payStrategy = payStrategy;
        }
    }
  1. 测试类

    public class MainTest {
        public static void main(String[] args) {
            //执行上下文
            PayStrategyContext payStrategyContext = new PayStrategyContext();
            IPayStrategy payStrategy = null;
            //1.支付宝支付
            payStrategy = new AliPayStrategy();
            payStrategyContext.setPayStrategy(payStrategy);
            payStrategyContext.excutePay();
            //2.微信支付
            payStrategy = new WexinPayStrategy();
            payStrategyContext.setPayStrategy(payStrategy);
            payStrategyContext.excutePay();
            //3.银联支付
            payStrategy = new EBankPayStrategy();
            payStrategyContext.setPayStrategy(payStrategy);
            payStrategyContext.excutePay();
        }
    }

执行结果:

另外

以上案例就是整个策略模式的架构,其实通过观察测试类可以发现,策略模式在编写执行逻辑时,只要改动的代码就是在创建对象这里:

那么,我们可以想到动态创建对象的方法之一:反射。如果这样,那就可以往数据库中存储这些策略的标识,然后新建一个工具类负责根据这些标识动态创建各个策略的不同对象。当然,一般来说,在系统中,这些策略应该时单例存在的,所以应该要把上述例子中的各个策略对象变为单例对象,已实现内存优化。

以下是优化后的版本类图:

这三种支付行为,其实在系统中可以使用单例,以此节省创建对象时间和内存空间。所以作出以下修改:

//这里就拿微信支付这种支付方式作为例子,其他支付方式的类跟这类似
//特别说明:因我们的策略想要让系统更加智能,选择对应策略。所以都需要时单例,并且提供 getInstance()
//           方法,以获取这个单例对象;
public class WexinPayStrategy implements IPayStrategy {
    private static WexinPayStrategy wexinPayStrategy;
      //单例的基本特点:私有化构造,提供获取单例的静态方法
    private WexinPayStrategy() {
    }
      //给外界提供单例
    public static WexinPayStrategy getInstance() {
        if (null == wexinPayStrategy) {
            wexinPayStrategy = new WexinPayStrategy();
        }
        return wexinPayStrategy;
    }
      //实现方法
    @Override
    public void pay() {
        System.out.println("微信支付.");
    }
}

以后的支付方式,如需拓展,就添加一个单例类,提供 getInstance()方法,实现 pay() 支付方法,并且将这个类放在 特定的包路径下,这样,我们就可以将支付方式字符串写入配置文件或者存储到数据库中,再利用工具类给context上下文提供这种支付方式的单例对象, context 利用这个单例对象,调用 pay() 方法,以此实现真正支付。

下面是这个工具类:

public class PayStrategyUtils {
    public static IPayStrategy getPayStrategy(String payType) {
        try {
              //这个路径需要根据你来定义,属于一种规则,这样一来,将所有策略都写在该包下即可
            String path = "com.pattern.demo.strategy.im." + payType.concat("PayStrategy");
            //反射,类加载器
            Class<?> clazz = Class.forName(path);
              //调用instance()方法获取单例对象
            IPayStrategy instance = (IPayStrategy) clazz.getDeclaredMethod("getInstance").invoke(null, null);
            return instance;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Load [" + payType.concat("Strategy") + "] Error :", e);
        }
    }
}

测试类可以变成:

public class MainTest {
    public static void main(String[] args) {
        //执行上下文
        PayStrategyContext payStrategyContext = new PayStrategyContext();
        IPayStrategy payStrategy = null;
        //1.1支付宝支付
//        payStrategy = new AliPayStrategy();
//        payStrategyContext.setPayStrategy(payStrategy);
//        payStrategyContext.excutePay();
//
//        //1.2微信支付
//        payStrategy = new WexinPayStrategy();
//        payStrategyContext.setPayStrategy(payStrategy);
//        payStrategyContext.excutePay();
//
//        //1.3银联支付
//        payStrategy = new EBankPayStrategy();
//        payStrategyContext.setPayStrategy(payStrategy);
//        payStrategyContext.excutePay();
//        1.1支付宝支付
        payStrategy = PayStrategyUtils.getPayStrategy("Ali");
        payStrategyContext.setPayStrategy(payStrategy);
        payStrategyContext.excutePay();
//        1.2微信支付
        payStrategy = PayStrategyUtils.getPayStrategy("Wexin");
        payStrategyContext.setPayStrategy(payStrategy);
        payStrategyContext.excutePay();
//        1.3银联支付
        payStrategy = PayStrategyUtils.getPayStrategy("EBank");
        payStrategyContext.setPayStrategy(payStrategy);
        payStrategyContext.excutePay();
    }
}

到这里就会发现,我们的策略就只需要在业务层传入一个参数即可,这个参数可以存储在数据库,也可以是写在文件中。这样如果后期需要添加一种策略,只需要添加一个策略的实现(就是类似上面的微信 WexinPayStrategy,支付宝 AliPayStrategy这些),再添加一个策略参数到配置文件或数据库即可(就是 Ali,Wexin,EBank)。

总结

文章主要介绍策略模式的应用场景,和一些基本架构。我看过一些资料,可能在类图中并没有工具类,但是为了简便,所以加了这一点。所有的设计模式,使用到对的场景就是最好的,在项目中会遇到类似业务,可以考虑使用,以减少后期的时间成本,经历成本。

文字比较啰嗦,很多也没阐述清楚,所以提供了这个demo的 Github地址,点击进入

posted @ 2018-02-02 15:51  星尘h  阅读(5452)  评论(0编辑  收藏  举报