Skip to content

1. 策略模式(Strategy Pattern)

  • 1.是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理,最终可以实现解决多重if判断问题。
  • 2.属于行为型模式。
  • 3.在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
  1. 环境(Context)角色:持有一个 Strategy 的引用。
  2. 抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
  3. 具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。

2. 应用场景

聚合支付平台

  • 比如支付宝、微信支付、银联支付等

3. 优缺点

java
public String toPayHtml(String payCode){
    if(payCode.equals("Ali_PAY")){
        return  "调用支付宝接口...";
    }
    if(payCode.equals("WECHAT_PAY")){
        return  "调用小米支付接口";
    }
    if(payCode.equals("UNION_PAY")){
        return  "调用银联支付接口...";
    }
    return  "未找到该接口...";
}

优点:

  • 算法可以自由切换(高层屏蔽算法,角色自由切换)。
  • 扩展性好(可自由添加取消算法 而不影响整个功能)。

缺点:

  • 后期维护不同策略类是非常多。
  • 定义类比较多。
  • 代码量增大。

4. 实现步骤

1. 定义策略接口

该接口方法中包含策略中实现的共同行为。

2. 实现不同的策略类

这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。

3. 利用多态或其他方式调用策略

  • 工厂 + 枚举
  • 数据库

5. 案例 1: 工厂 + 枚举

1. Maven 项目依赖

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>strategy-desgin-patterns</artifactId>

    <dependencies>
        <!-- sprinboot web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.10</version>
        </dependency>

        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.1.1</version>
        </dependency>

        <!-- mysql 依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

    </dependencies>

</project>

2. PayStrategy(抽象角色)

  • 定义共同的使用的行为方式
java
package com.strategy.strategy;

/**
 * Created by Calvin on 2019/5/8
 *  策略模式: 共同定义的骨架
 */
public interface PayStrategy {

    /**
     *  共同行为
     *
     * @return
     */
    public String toPayHtml();
}

3. ConcreteStrategy (具体实现角色)

  • 具体实现是支付渠道:AliPay微信银联
java
package com.pay.strategy;

import com.pay.strategy.PayStrategy;
import org.springframework.stereotype.Component;

/**
 * Created by Calvin on 2019/5/8
 * 通过AliPay 策略实现共同的骨架
 */
@Component
public class AliPayStrategy implements PayStrategy {

    public String toPayHtml() {
        return "调用支付宝接口...";
    }
}
java
package com.pay.strategy;

import org.springframework.stereotype.Component;

/**
 * Created by Calvin on 2019/5/8
 * 通过银联策略实现共同的骨架
 */
@Component
public class UnionPayStrategy implements PayStrategy {

    public String toPayHtml() {
        return "调用银联接口...";
    }
}
java
package com.pay.strategy;

import org.springframework.stereotype.Component;

/**
 * Created by Calvin on 2019/5/11
 * 通过微信支付策略实现共同骨架
 */
@Component
public class WechatPayStrategy implements PayStrategy {

    @Override
    public String toPayHtml() {
        return "调用微信接口...";
    }
}

4. PayContextService (上下文)

  • 创建枚举类 PayCodeEnum

    java
    package com.pay.enumeration;
    
    import lombok.AllArgsConstructor;
    import lombok.Getter;
    
    @Getter
    @AllArgsConstructor
    public enum PayCodeEnum {
    
        WECHAT_PAY("com.pay.strategy.WechatPayStrategy"),
        ALI_PAY("com.pay.strategy.AliPayStrategy"),
        UNION_PAY("com.pay.strategy.UnionPayStrategy"),
        ;
    
        /** class 完整地址 **/
        private String className;
    
    }
  • 创建工厂类 StrategyFactory 类,根据支付编码,获取对应的策略类名,再通过反射实例化。

    java
    package com.pay.strategy.factory;
    
    import com.pay.enumeration.PayCodeEnum;
    import com.pay.strategy.PayStrategy;
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    public class StrategyFactory {
    
        /**
         * 通过工厂,获取对应的策略
         * @param payCode 支付码
         * @return 对应的策略
         */
        public static PayStrategy getPayStrategy(String payCode) {
    
            // 1. 获取枚举中的ClassName
            String className = PayCodeEnum.valueOf(payCode).getClassName();
    
            PayStrategy payStrategy = null;
            try {
                // 2. 使用java 反射技术初始化类
                payStrategy = (PayStrategy)Class.forName(className).newInstance();
            } catch (Exception e) {
                System.out.println("反射类不存在");
                log.error("============== 反射类不存在,错误:{} ====================", e.getMessage());
            }
            return payStrategy;
        }
    
    }
  • 创建具体业务实现类 PayServiceImpl

    java
    package com.pay.service.impl;
    
    import com.pay.entity.PaymentChannelEntity;
    import com.pay.mapper.PaymentChannelMapper;
    import com.pay.service.PayService;
    import com.pay.strategy.PayStrategy;
    import com.pay.strategy.factory.StrategyFactory;
    import com.pay.utils.ContextUtils;
    import org.apache.commons.lang.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    /**
     * Created by Calvin on 2019/5/11
     */
    @Component
    public class PayServiceImpl implements PayService {
    
        @Override
        public String toPayHtml2(String payCode) {
            PayStrategy payStrategy = StrategyFactory.getPayStrategy(payCode);
            if (null == payStrategy) {
                return "暂不支持该渠道支付方式";
            }
            return payStrategy.toPayHtml();
        }
    }

5. 扩展 ContextUtils 类

java
package com.pay.utils;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * Created by Calvin on 2019/5/9
 */
@Component
public class ContextUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    //获取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    //通过name获取 Bean.
    public static Object getBean(String name){
        return getApplicationContext().getBean(name);
    }

    //通过class获取Bean.
    public static <T> T getBean(Class<T> clazz){
        return getApplicationContext().getBean(clazz);
    }

    //通过name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name,Class<T> clazz){
        return getApplicationContext().getBean(name, clazz);
    }

}

6. 案例 2:  数据库方式

1. 执行 SQL 语句

  • 创建数据库表 payment_channel:支付渠道表

  • 添加对应支付渠道:支付宝微信银联

    mysql
    DROP TABLE IF EXISTS `payment_channel`;
    CREATE TABLE `payment_channel` (
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
      `channel_name` varchar(32) NOT NULL COMMENT '渠道名称',
      `channel_id` varchar(32) NOT NULL COMMENT '渠道ID',
      `strategy_bean_id` varchar(255) DEFAULT NULL COMMENT '策略执行beanid',
      PRIMARY KEY (`ID`,`CHANNEL_ID`)
    ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT='支付渠道';
    
    -- ----------------------------
    -- Records of payment_channel
    -- ----------------------------
    INSERT INTO `payment_channel` VALUES ('1', '支付宝渠道', 'ALI_PAY', 'aliPayStrategy');
    INSERT INTO `payment_channel` VALUES ('2', '微信支付渠道', 'WECHAT_PAY', 'wechatPayStrategy');
    INSERT INTO `payment_channel` VALUES ('3', '银联支付渠道', 'UNION_PAY', 'unionPayStrategy');

2. 新建实体类

java
package com.pay.entity;

import lombok.Data;

/**
 * Created by Calvin on 2019/5/8
 */
@Data
public class PaymentChannelEntity {

    /** ID */
    private Integer id;

    /** 渠道名称 */
    private String channelName;

    /** 渠道ID */
    private String channelId;

    /** 策略执行BeanId */
    private String strategyBeanId;
}

3. 创建数据库访问层 PaymentChannelMapper

  • 根据支付编号,查询对应支付渠道

    java
    package com.pay.mapper;
    
    import com.pay.entity.PaymentChannelEntity;
    import org.apache.ibatis.annotations.Select;
    
    /**
     * Created by Calvin on 2019/5/8
     */
    public interface PaymentChannelMapper {
    
        /**
         * 根据支付编号,查询对应支付渠道
         * @param payCode 支付编号
         * @return 支付渠道
         */
        @Select("\n" +
                "SELECT  " +
                "id AS id," +
                "channel_name AS channelName ," +
                "channel_id AS channelId," +
                "strategy_bean_id AS strategyBeanId\n" +
                "FROM payment_channel " +
                "WHERE channel_id=#{payCode}")
        PaymentChannelEntity getPaymentChannel(String payCode);
    }

4. PayStrategy(抽象角色)

  • 定义共同的使用的行为方式

    java
    package com.strategy.strategy;
    
    /**
     * Created by Calvin on 2019/5/8
     *  策略模式: 共同定义的骨架
     */
    public interface PayStrategy {
    
        /**
         *  共同行为
         *
         * @return
         */
        public String toPayHtml();
    }

5. ConcreteStrategy (具体实现角色)

  • 具体实现是支付渠道:AliPay微信银联

    java
    package com.pay.strategy;
    
    import com.pay.strategy.PayStrategy;
    import org.springframework.stereotype.Component;
    
    /**
     * Created by Calvin on 2019/5/8
     * 通过AliPay 策略实现共同的骨架
     */
    @Component
    public class AliPayStrategy implements PayStrategy {
    
        public String toPayHtml() {
            return "调用支付宝接口...";
        }
    }
    java
    package com.pay.strategy;
    
    import org.springframework.stereotype.Component;
    
    /**
     * Created by Calvin on 2019/5/8
     * 通过银联策略实现共同的骨架
     */
    @Component
    public class UnionPayStrategy implements PayStrategy {
    
        public String toPayHtml() {
            return "调用银联接口...";
        }
    }
    java
    package com.pay.strategy;
    
    import org.springframework.stereotype.Component;
    
    /**
     * Created by Calvin on 2019/5/11
     * 通过微信支付策略实现共同骨架
     */
    @Component
    public class WechatPayStrategy implements PayStrategy {
    
        @Override
        public String toPayHtml() {
            return "调用微信接口...";
        }
    }

6. PayContextService (上下文)

  • 创建具体业务实现类 PayServiceImpl

    java
    package com.pay.service.impl;
    
    import com.pay.entity.PaymentChannelEntity;
    import com.pay.mapper.PaymentChannelMapper;
    import com.pay.service.PayService;
    import com.pay.strategy.PayStrategy;
    import com.pay.strategy.factory.StrategyFactory;
    import com.pay.utils.ContextUtils;
    import org.apache.commons.lang.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    /**
     * Created by Calvin on 2019/5/11
     */
    @Component
    public class PayServiceImpl implements PayService {
    
        @Autowired
        private PaymentChannelMapper paymentChannelMapper;
    
        @Override
        public String toPayHtml1(String payCode) {
            // 1.使用payCode 参数查询数据库获取beanId
            PaymentChannelEntity paymentChannelEntity = paymentChannelMapper.getPaymentChannel(payCode);
            if (paymentChannelEntity == null) return "暂不支持该渠道支付方式";
            // 2.获取Bean的ID之后,使用Spring容器获取实例对象
            String strategyBeanId = paymentChannelEntity.getStrategyBeanId();
            if(StringUtils.isEmpty(strategyBeanId)){
                return "该渠道没有BeanID";
            }
            // 3.执行该实现的方法
            PayStrategy payStrategy = ContextUtils.getBean(strategyBeanId, PayStrategy.class);
            return payStrategy.toPayHtml();
        }
    
        @Override
        public String toPayHtml2(String payCode) {
            PayStrategy payStrategy = StrategyFactory.getPayStrategy(payCode);
            if (null == payStrategy) {
                return "暂不支持该渠道支付方式";
            }
            return payStrategy.toPayHtml();
        }
    }

7. 扩展工具类 ContextUtils

  • 用于获取 Spring 容器 Bean 的实例化。

    java
    package com.pay.utils;
    
    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.stereotype.Component;
    
    /**
     * Created by Calvin on 2019/5/9
     */
    @Component
    public class ContextUtils implements ApplicationContextAware {
    
        private static ApplicationContext applicationContext;
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
    
        //获取applicationContext
        public static ApplicationContext getApplicationContext() {
            return applicationContext;
        }
    
        //通过name获取 Bean.
        public static Object getBean(String name){
            return getApplicationContext().getBean(name);
        }
    
        //通过class获取Bean.
        public static <T> T getBean(Class<T> clazz){
            return getApplicationContext().getBean(clazz);
        }
    
        //通过name,以及Clazz返回指定的Bean
        public static <T> T getBean(String name,Class<T> clazz){
            return getApplicationContext().getBean(name, clazz);
        }
    }

7. 实战演示结果

代码链接:https://github.com/1016280226/design-patterns

实战一:工厂 + 枚举

实战二:数据库方式

8. 扩展: Spring 框架中使用的策略模式

ClassPathXmlApplicationContext:Spring 底层Resource接口采用策略模式。

1. SpringResource 接口提供了如下实现类:

  • UrlResource:访问网络资源的实现类。
  • ClassPathResource:访问类加载路径里资源的实现类。
  • FileSystemResource:访问文件系统里资源的实现类。
  • ServletContextResource:访问相对于 ServletContext 路径里的资源的实现类:
  • InputStreamResource:访问输入流资源的实现类。
  • ByteArrayResource:访问字节数组资源的实现类。

2. SpringBean 初始化 SimpleInstantiationStrategy

  • SimpleInstantiationStrategy 简单初始化策略
  • CglibSubclassingInstantiationStrategy CGLIB 初始化策略

9. 总结

生活版

  1. 《孙子兵法》为例,敌人使用什么招式,我用《孙子兵法》中的计谋来应付,每一个兵法就是策略。

  2. 诸葛亮的锦囊妙计,每一个锦囊就是一个策略。

  3. 旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。

专业版

  1. 含义:定义一系列的算法(兵法计谋实际操作步骤),把它们一个个封装起来(每一个兵法计谋的标题),并且使它们可相互替换(兵法计谋都可以替换)。

  2. 实现原理:表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。(一个行为,随着对象改变而执行对象行为也随之改变)

  3. 例如:你和你的前女友拍拖,会牵手、吃饭、逛街看电影这些行为;但你现在和你现女友,也会牵手、吃饭、逛街看电影这些行为;但是感觉不一样!