经典设计模式之策略模式【重构聚合支付平台,对接 (支付宝、微信、银联支付) 】
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、总结
优点
算法可以自由切换(高层屏蔽算法,角色自由切换)
避免使用多重条件判断(如果算法过多就会出现很多种相同的判断,很难维护)
扩展性好(可自由添加取消算法 而不影响整个功能)
缺点
策略类数量增多(每一个策略类复用性很小,如果需要增加算法,就只能新增类)
所有的策略类都需要对外暴露(使用的人必须了解使用策略,这个就需要其它模式来补充,比如工厂模式、代理模式)