一、设计模式
1. 为什么需要使用设计模式
使用设计模式可以重构整体架构代码、提交代码复用性、扩展性、减少代码冗余问题。
2. 设计模式的分类
创建型模式
工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
。
结构型模式
适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式
行为模式
策略模式、模板方法模式、观察者模式
、迭代子模式、责任链模式
、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
策略模式
3. 什么是策略模式
策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理,最终可以实现解决多重if判断问题。
1.环境(Context)角色:持有一个Strategy的引用。
2.抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
3.具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。
定义策略接口->实现不同的策略类->利用多态或其他方式调用策略
4. 为什么叫做策略模式
每个if判断都可以理解为就是一个策略。
5. 策略模式优缺点
优点
算法可以自由切换(高层屏蔽算法,角色自由切换)
避免使用多重条件判断(如果算法过多就会出现很多种相同的判断,很难维护)
扩展性好(可自由添加取消算法 而不影响整个功能)
缺点
策略类数量增多(每一个策略类复用性很小,如果需要增加算法,就只能新增类)
所有的策略类都需要对外暴露(使用的人必须了解使用策略,这个就需要其它模式来补充,比如工厂模式、代理模式)
6. 策略模式应用场景
聚合支付平台
比如搭建聚合支付平台的时候,这时候需要对接很多第三方支付接口,比如支付宝、微信支付、小米支付等。
通过传统if代码判断的,后期的维护性非常差!
这时候可以通过策略模式解决多重if判断问题。
7. Spring框架中使用的策略模式
ClassPathXmlApplicationContext Spring底层Resource接口采用策略模式
Spring 为 Resource 接口提供了如下实现类:
UrlResource:访问网络资源的实现类。
ClassPathResource:访问类加载路径里资源的实现类。
FileSystemResource:访问文件系统里资源的实现类。
ServletContextResource:访问相对于 ServletContext 路径里的资源的实现类:
InputStreamResource:访问输入流资源的实现类。
ByteArrayResource:访问字节数组资源的实现类。
1、new ClassPathXmlApplicationContext("");
2.进入该构造函数
4.SpringBean初始化 SimpleInstantiationStrategy
SimpleInstantiationStrategy 简单初始化策略
CglibSubclassingInstantiationStrategy CGLIB初始化策略
二、策略模式~聚合短信服务
2.1. 依赖引入
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.gblfy</groupId>
<artifactId>design-pattern</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>design-pattern</name>
<description>design-pattern</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--字符串工具类-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!--数据json处理-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.79</version>
</dependency>
<!--SpringMVC-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.2. 抽象公共行为接口
MsgStrategy
package com.gblfy.strategy;
/**
* 抽象公共行为接口
*
* @author gblfy
* @date 2022-03-13
*/
public interface MsgStrategy {
/**
* 共同行为方法
*
* @param phone
* @return
*/
String sendMsg(String phone);
}
2.3. 具体策略接口实现类
调用(阿里云)短信息服务
package com.gblfy.strategy.impl;
import com.gblfy.strategy.MsgStrategy;
import org.springframework.stereotype.Service;
/**
* 调用(阿里云)短信息服务
*
* @author gblfy
* @date 2022-03-13
*/
@Service
public class AliYunStrategy implements MsgStrategy {
@Override
public String sendMsg(String phone) {
return "调用(阿里云)短信息服务";
}
}
调用(华为云)短信息服务
package com.gblfy.strategy.impl;
import com.gblfy.strategy.MsgStrategy;
import org.springframework.stereotype.Service;
/**
* 调用(华为云)短信息服务
*
* @author gblfy
* @date 2022-03-13
*/
@Service
public class HuaWeiStrategy implements MsgStrategy {
@Override
public String sendMsg(String phone) {
return "调用(华为云)短信息服务";
}
}
调用(腾讯云)短信息服务
package com.gblfy.strategy.impl;
import com.gblfy.strategy.MsgStrategy;
import org.springframework.stereotype.Service;
/**
* 调用(腾讯云)短信息服务
*
* @author gblfy
* @date 2022-03-13
*/
@Service
public class TencentStrategy implements MsgStrategy {
@Override
public String sendMsg(String phone) {
return "调用(腾讯云)短信息服务";
}
}
2.4. 策略枚举
package com.gblfy.enums;
/**
* 策略枚举,存放所有策略的实现
*
* @author gblfy
* @date 2022-03-13
*/
public enum SmsEnumStrategy {
/**
* 支付宝短信
*/
ALI_SMS("com.gblfy.strategy.impl.AliYunStrategy"),
/**
* 华为云短信
*/
HUAWEI_SMS("com.gblfy.strategy.impl.HuaWeiStrategy"),
/**
* 腾讯云短信
*/
TENCENT_SMS("com.gblfy.strategy.impl.TencentStrategy");
/**
* class 名称
*/
private String className;
SmsEnumStrategy(String className) {
this.setClassName(className);
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
}
2.5. 获取具体策略实现
package com.gblfy.strategy;
import com.gblfy.factory.StrategyFactory;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
/**
* 获取具体策略实现
*
* @author gblfy
* @date 2022-03-13
*/
@Component
public class SmsContextStrategy {
/**
* 获取具体策略实现
*/
public String getStrategy(String strategyId, String phone) {
if (StringUtils.isEmpty(strategyId)) {
return "paycode 不能为空";
}
// 第1种:使用策略工厂获取具体策略的实现
MsgStrategy msgStrategy = StrategyFactory.getPayStrategy(strategyId);
if (msgStrategy == null) {
return ",没有找到具体策略的实现...";
}
return msgStrategy.sendMsg(phone);
}
}
2.6. 策略工厂
package com.gblfy.factory;
import com.gblfy.enums.SmsEnumStrategy;
import com.gblfy.strategy.MsgStrategy;
/**
* 使用策略工厂获取具体策略实现
*
* @author gblfy
* @date 2022-03-13
*/
public class StrategyFactory {
//工厂初始化
public static MsgStrategy getPayStrategy(String strategyType) {
try {
// 1.获取枚举中className
String className = SmsEnumStrategy.valueOf(strategyType).getClassName();
// 2.使用java反射技术初始化类
return (MsgStrategy) Class.forName(className).newInstance();
} catch (Exception e) {
return null;
}
}
}
2.7. 聚合短信服务测试
/**
* 测试链接:
* http://localhost:8080/sendMsgByEnumSfactory?strategyId=ALI_SMS&phone=123456
* http://localhost:8080/sendMsgByEnumSfactory?strategyId=HUAWEI_SMS&phone=123456
* http://localhost:8080/sendMsgByEnumSfactory?strategyId=TENCENT_SMS&phone=123456
*/
package com.gblfy.controller;
import com.gblfy.strategy.SmsContextStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MsgController {
@Autowired
private SmsContextStrategy contextStrategy;
/**
* 使用枚举+工厂+策略模式实现聚合短信服务
*
* @param strategyId 策略ID
* @param phone 手机号码
* @return
*/
@GetMapping("/sendMsgByEnumSfactory")
public String sendMsgByEnumSfactory(@RequestParam("strategyId") String strategyId,
@RequestParam("phone") String phone) {
return contextStrategy.getStrategy(strategyId, phone);
}
/**
* 测试链接:
* http://localhost:8080/sendMsgByEnumSfactory?strategyId=ALI_SMS&phone=123456
* http://localhost:8080/sendMsgByEnumSfactory?strategyId=HUAWEI_SMS&phone=123456
* http://localhost:8080/sendMsgByEnumSfactory?strategyId=TENCENT_SMS&phone=123456
*/
}
三、聚合短信服务2
1.使用工厂模式初始化具体策略class
2.将所有具体实现的策略存放到map集合中(枚举类中)
3.key:ALI_PAY value:com.gblfy.service.AliPayStrategy
3.1. 策略工厂调整
package com.gblfy.factory;
import com.gblfy.strategy.MsgStrategy;
import com.gblfy.strategy.impl.AliYunStrategy;
import com.gblfy.strategy.impl.HuaWeiStrategy;
import com.gblfy.strategy.impl.TencentStrategy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class FactoryStrategy {
private static Map<String, MsgStrategy> msgStrategyMap = new ConcurrentHashMap<>();
static {
msgStrategyMap.put("huawei", new HuaWeiStrategy());
msgStrategyMap.put("tencent", new TencentStrategy());
msgStrategyMap.put("aliyun", new AliYunStrategy());
}
public static MsgStrategy getContextStrategy(String strategyId) {
return msgStrategyMap.get(strategyId);
}
}
3.2. 聚合短信测试
package com.gblfy.controller;
import com.gblfy.factory.FactoryStrategy;
import com.gblfy.strategy.MsgStrategy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MsgController {
/**
* 使用工厂+策略模式实现聚合短信服务
*
* @param strategyId
* @param phone
* @return
*/
@GetMapping("/sendMsgByfactory")
public String sendMsgByfactory(@RequestParam("strategyId") String strategyId,
@RequestParam("phone") String phone) {
MsgStrategy contextStrategy = FactoryStrategy.getContextStrategy(strategyId);
return contextStrategy.sendMsg(phone);
}
/**
* 测试链接:
* http://localhost:8080/sendMsgByfactory?strategyId=huawei&phone=123456
* http://localhost:8080/sendMsgByfactory?strategyId=tencent&phone=123456
* http://localhost:8080/sendMsgByfactory?strategyId=aliyun&phone=123456
*/
}
四、聚合短信3
使用springIOC代替反射,提高效率,动态切换(无需改动项目)
4.1. 策略上下文
package com.gblfy.strategy;
import com.gblfy.utils.SpringContextUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
@Component
public class StrategyContext {
public MsgStrategy getStrategy(String strategyId) {
if (StringUtils.isEmpty(strategyId)) {
return null;
}
return SpringContextUtils.getBean(strategyId, MsgStrategy.class);
}
}
4.2. SpringContext上下文工具类
package com.gblfy.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* 获取Spring上下文工具类
*
* @author gblfy
* @date 2022-03-13
*/
@Component
public class SpringContextUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtils.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);
}
}
4.3. 聚合短信测试
/**
* 使用工厂+策略模式+SpringIOC实现聚合短信服务
*
* @param strategyId
* @param phone
* @return
*/
@GetMapping("/sendMsgBySpringIOC")
public String sendMsgBySpringIOC(@RequestParam("strategyId") String strategyId,
@RequestParam("phone") String phone) {
MsgStrategy contextStrategy = strategyContext.getStrategy(strategyId);
return contextStrategy.sendMsg(phone);
}
/**
* 测试链接:
* http://localhost:8080/sendMsgBySpringIOC?strategyId=aliYunStrategy&phone=123456
* http://localhost:8080/sendMsgBySpringIOC?strategyId=huaWeiStrategy&phone=123456
* http://localhost:8080/sendMsgBySpringIOC?strategyId=tencentStrategy&phone=123456
*/
五、聚合短信+聚合支付(企业内部升级)
5.1. 相关SQL语句
drop database IF EXISTS `design_pattern`;
create database `design_pattern`;
use `design_pattern`;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for gblfy_strategy
-- ----------------------------
DROP TABLE IF EXISTS `gblfy_strategy`;
CREATE TABLE `gblfy_strategy` (
`id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',
`strategy_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '策略名称',
`strategy_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '策略ID',
`strategy_type` int NOT NULL COMMENT '策略类型(0-支付服务,1-短信服务)',
`strategy_bean_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '策略执行beanid实例',
`deleted` int NOT NULL COMMENT '逻辑删除字段(0-有效 1-无效,默认为0)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '策略配置表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of gblfy_strategy
-- ----------------------------
INSERT INTO `gblfy_strategy` VALUES (6, '腾讯云短信', 'tencent_sms', 1, 'tencentStrategy', 0);
INSERT INTO `gblfy_strategy` VALUES (7, '阿里云短信', 'aliYun_sms', 1, 'aliYunStrategy', 0);
INSERT INTO `gblfy_strategy` VALUES (8, '华为云短信', 'huaWei_sms', 1, 'huaWeiStrategy', 0);
INSERT INTO `gblfy_strategy` VALUES (9, '阿里支付', 'ali_pay', 0, 'aliPayStrategy', 0);
INSERT INTO `gblfy_strategy` VALUES (10, '银联支付', 'yinlian_pay', 0, 'unionPayStrategy', 0);
SET FOREIGN_KEY_CHECKS = 1;
5.2. 策略实体
package com.gblfy.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
@Data
@TableName("gblfy_strategy")
public class GblfyStrategy {
// 策略配置主键
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
//策略名称(阿里云短信、银联支付)
@TableField("strategy_name")
private String strategyName;
//策略ID
@TableField("strategy_id")
private String strategyId;
//策略类型(发短信或者调用支付)
@TableField("strategy_type")
private String strategyType;
//策略具体执行beanId
@TableField("strategy_bean_id")
private String strategyBeanId;
//逻辑删除字段
@TableLogic
private Integer deleted;
}
5.3. 策略接口
package com.gblfy.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gblfy.entity.GblfyStrategy;
/**
* 策略接口
*
* @author gblfy
* @date 2022-03-13
*/
public interface StragegyMapper extends BaseMapper<GblfyStrategy> {
}
5.4.策略上下文
package com.gblfy.strategy;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.gblfy.entity.GblfyStrategy;
import com.gblfy.mapper.StragegyMapper;
import com.gblfy.utils.SpringContextUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class StrategyContext {
@Autowired
private StragegyMapper stragegyMapper;
// public MsgStrategy getStrategy(String strategyId) {
// if (StringUtils.isEmpty(strategyId)) {
// return null;
// }
// return SpringContextUtils.getBean(strategyId, MsgStrategy.class);
// }
// public <T> T getStrategy(String stragegyId,Class<T> t){
// if (StringUtils.isEmpty(stragegyId)){
// return null;
// }
// return SpringContextUtils.getBean(stragegyId, t);
// }
public <T> T getStrategy(String stragegyId, String stragegyType, Class<T> t) {
if (StringUtils.isEmpty(stragegyId)) {
return null;
}
if (StringUtils.isEmpty(stragegyType)) {
return null;
}
if (t == null) {
return null;
}
//根据策略id和策略类型查询具体策略实例beanId
GblfyStrategy gblfyStrategy = stragegyMapper.selectOne(
new QueryWrapper<GblfyStrategy>()
.lambda()
.eq(GblfyStrategy::getStrategyId, stragegyId)
.eq(GblfyStrategy::getStrategyType, stragegyType));
if (gblfyStrategy == null) {
return null;
}
String strategyBeanId = gblfyStrategy.getStrategyBeanId();
if (StringUtils.isEmpty(strategyBeanId)) {
return null;
}
return SpringContextUtils.getBean(strategyBeanId, t);
}
}
5.5. 聚合短信和聚合支付测试
/**
* 多个不同服务(短信+支付)抽象封装
* mysql+SpringIOC+策略模式实现聚合短信服务和聚合支付服务
*
* @param strategyId
* @param stragegyType
* @param phone
* @return
*/
@GetMapping("/sendMsgByMysqlAndSpringIOC")
public String sendMsgBySpringIOC(@RequestParam("strategyId") String strategyId,
@RequestParam("stragegyType") String stragegyType,
@RequestParam("phone") String phone) {
MsgStrategy strategy = strategyContext.getStrategy(strategyId, stragegyType, MsgStrategy.class);
if (strategy == null) {
return "当前渠道已关闭或者不存在,请核实!";
}
return strategy.sendMsg(phone);
}
/**
* 测试链接(聚合短信):
* http://localhost:8080/sendMsgByMysqlAndSpringIOC?strategyId=ali_pay&stragegyType=1&phone=123456
* http://localhost:8080/sendMsgByMysqlAndSpringIOC?strategyId=ali_pay&stragegyType=1&phone=123456
* http://localhost:8080/sendMsgByMysqlAndSpringIOC?strategyId=ali_pay&stragegyType=1&phone=123456
*
* 测试链接(聚合支付):
* http://localhost:8080/sendMsgByMysqlAndSpringIOC?strategyId=ali_pay&stragegyType=0&phone=123456
*/
5.6. mapper扫描配置
package com.gblfy.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.gblfy.mapper")
public class MybatisPlusConfig {
}
5.7. 依赖
<!--mybatis-plus 持久化-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
5.8. yml配置
server:
port: 8080
spring:
datasource:
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/design_pattern?useUnicode=true&characterEncoding=UTF-8
logging:
level:
com.gblfy.mapper: DEBUG
mybatis-plus:
configuration:
log-impl:
mapper-locations: classpath:mappers/*.xml