经典设计模式之策略模式【重构聚合支付平台,对接 (支付宝、微信、银联支付) 】

1、为什么要使用设计模式

使用设计模式可以重构整体架构代码、提交代码复用性、扩展性、减少代码冗余问题。Java高级工程师必备的技能!

2、什么是策略模式

        策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理,最终可以实现解决多重if判断问题。

1、环境(Context)角色:持有一个Strategy的引用。

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

3、具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。

定义策略接口->实现不同的策略类->利用多态或其他方式调用策略。

3、策略模式应用场景

在搭建聚合支付平台时,需要对接很多第三方的支付接口,比如支付宝支付、微信支付、银联支付等,如果使用传统的if判断代码,这对于后期的项目维护性是非常差的。

public String toPayHtml(String payCode){
    if(payCode.equals("ali_pay")){
        return  "调用支付宝接口...";
    }
    if(payCode.equals("union_pay")){
        return  "调用银联支付接口";
    }
    if(payCode.equals("weChat_pay")){
        return  "调用微信支付接口...";
    }
    return  "未找到该接口...";
}

所以在这个时候就可以使用策略模式来解决这样的多重if判断问题。

4、策略模式架构图

经典设计模式之策略模式【重构聚合支付平台,对接 (支付宝、微信、银联支付) 】

5、策略模式环境搭建

  • maven依赖信息

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
    </parent>
    <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>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>
    </dependencies>
  • PayStrategy(抽象角色)

/**
 * @Classname PayStrategy
 * @Description 共同算法的定义骨架
 * @Date 2019/5/14 15:01
 * @Created by mark
 */
public interface PayStrategy {
    String toPayHtml();
}
  • ConcreteStrategy (具体实现角色)

/**
 * @Classname AliPayStrategy
 * @Description 支付宝支付
 * @Date 2019/5/14 15:04
 * @Created by mark
 */
@Component
public class AliPayStrategy implements PayStrategy {
    @Override
    public String toPayHtml() {
        return "调用支付宝支付接口...";
    }
}
/**
 * @Classname UnionPayStrategy
 * @Description 银联支付
 * @Date 2019/5/14 15:05
 * @Created by mark
 */
@Component
public class UnionPayStrategy implements PayStrategy {
    @Override
    public String toPayHtml() {
        return "调用银联支付接口...";
    }
}
/**
 * @Classname WeChatPayStrategy
 * @Description 微信支付
 * @Date 2019/5/14 15:04
 * @Created by mark
 */
@Component
public class WeChatPayStrategy implements PayStrategy {
    @Override
    public String toPayHtml() {
        return "调用微信支付接口...";
    }
}
  • PayContextStrategy (上下文)

/**
 * @Classname PayContextStrategy
 * @Description 上下文
 * @Date 2019/5/14 15:06
 * @Created by mark
 */
@Component
public class PayContextStrategy {
    @Autowired
    private PaymentChannelMapper paymentChannelMapper;

    public String toPayHtml(String payCode){
        //1、参数验证
        if(StringUtils.isEmpty(payCode)){
            return BaseReturnInfo.PAYCODE_IS_BLANK;
        }
        //2、使用payCode查询
        PaymentChannelEntity paymentChannel = paymentChannelMapper.getPaymentChannel(payCode);
        if(paymentChannel == null){
            return BaseReturnInfo.PAYMENTCHANNEL_IS_NULL;
        }
        //3、获取策略的beanid
        String strategyBeanId = paymentChannel.getStrategyBeanId();
        if (StringUtils.isEmpty(strategyBeanId)) {
            return BaseReturnInfo.STRATEGYBEANID_IS_BLANK;
        }
        //4、根据beanid在spring容器中查找到对应的bean
        PayStrategy payStrategy = SpringUtils.getBean(strategyBeanId, PayStrategy.class);
        //5、执行具体的策略
        return payStrategy.toPayHtml();
    }
}
  • SpringUtils

@Component
public class SpringUtils 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);
    }

}
  • BaseReturnInfo

/**
 * @Classname BaseReturnInfo
 * @Description TODO
 * @Date 2019/5/14 15:29
 * @Created by mark
 */
public final class BaseReturnInfo {
    public static final String  PAYMENTCHANNEL_IS_NULL="没有该渠道信息";

    public static final String STRATEGYBEANID_IS_BLANK="该渠道没有配置beanid";

    public static final String PAYCODE_IS_BLANK="渠道code不能为空";
}
  • 数据库访问层

经典设计模式之策略模式【重构聚合支付平台,对接 (支付宝、微信、银联支付) 】

sql脚本:

/*
Navicat MySQL Data Transfer

Source Server         : localhost
Source Server Version : 50539
Source Host           : localhost:3306
Source Database       : design_pattern

Target Server Type    : MYSQL
Target Server Version : 50539
File Encoding         : 65001

Date: 2019-05-14 16:30:37
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for payment_channel
-- ----------------------------
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`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='支付渠道 ';

-- ----------------------------
-- Records of payment_channel
-- ----------------------------
INSERT INTO `payment_channel` VALUES ('4', '支付宝渠道', 'ali_pay', 'aliPayStrategy');
INSERT INTO `payment_channel` VALUES ('5', '银联支付渠道', 'union_pay', 'unionPayStrategy');
INSERT INTO `payment_channel` VALUES ('6', '微信支付渠道', 'wechat_pay', 'weChatPayStrategy');
  • Entity

@Data
public class PaymentChannelEntity {
   /** ID */
   private Integer id;
   /** 渠道名称 */
   private String channelName;
   /** 渠道ID */
   private String channelId;
   /**
    * 策略执行beanId
    */
   private String strategyBeanId;
}
  • Mapper

public interface PaymentChannelMapper {
     @Select("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);
}
  • controller

@RestController
public class PayController {
    @Autowired
    private PayContextStrategy payContextStrategy;

    @RequestMapping("/toPayHtml")
    public String toPayHtml(String payCode) {
        return  payContextStrategy.toPayHtml(payCode);
    }
}
  • application.yml

###服务启动端口号
server:
  port: 8080
spring:
###数据库相关连接      
  datasource:
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/design_pattern?useUnicode=true&characterEncoding=UTF-8
####打印MyBatias日志    
logging:
  level:
  ### 开发环境使用DEBUG 生产环境info或者error
    com.xwhy.mapper: DEBUG
  • 启动类

/**
 * @Classname StrategyApp
 * @Description TODO
 * @Date 2019/5/14 15:00
 * @Created by mark
 */
@SpringBootApplication
@MapperScan("com.xwhy.mapper")
public class StrategyApp {
    public static void main(String[] args) {
        SpringApplication.run(StrategyApp.class,args);
    }
}
  • 启动效果截图:

经典设计模式之策略模式【重构聚合支付平台,对接 (支付宝、微信、银联支付) 】

经典设计模式之策略模式【重构聚合支付平台,对接 (支付宝、微信、银联支付) 】

经典设计模式之策略模式【重构聚合支付平台,对接 (支付宝、微信、银联支付) 】

6、总结

优点

    算法可以*切换(高层屏蔽算法,角色*切换)

    避免使用多重条件判断(如果算法过多就会出现很多种相同的判断,很难维护)

    扩展性好(可*添加取消算法 而不影响整个功能)

缺点

    策略类数量增多(每一个策略类复用性很小,如果需要增加算法,就只能新增类)

    所有的策略类都需要对外暴露(使用的人必须了解使用策略,这个就需要其它模式来补充,比如工厂模式、代理模式)