设计模式实战第一讲
本篇从实战代码角度详解策略模式、模板模式
这两种设计模式。
软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。
从某种意义上说:设计模式是前辈总结出来的开发经验,是对面向对象设计中反复出现的问题的解决方案。
常用的23种设计模式分三类:
设计模式有其基本的六大原则。常见的23种设计模式即为这六大原则的具体实现。
除了上面的23种设计模式外,还有一些新的设计模式。如过滤器模式、空对象模式等等,这里就不一一列举。
介绍:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。可用于消除if else 分支代码。
应用场景:
支付场景:微信支付、支付宝支付都是一种策略实现。
订单优惠场景:满减、折扣、满减再折扣都是其具体的实现策略。
优点:
角色:
Context:上下文类。维护策略对象的引用。
Strategy:策略接口类。定义算法公共接口。
StrategyA:策略实现类。封装具体的算法或行为。
代码示例:
public interface Strategy {
int compute(Object params);
}
public class StrategyA implements Strategy {
@Override
public int compute(Object params) {
//策略A算法逻辑
return 0;
}
}
public class StrategyB implements Strategy {
@Override
public int compute(Object params) {
//策略B算法逻辑
return 1;
}
}
public class Context {
//引用策略对象
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void exec(Object params) {
//执行策略方法
strategy.compute(params);
}
}
介绍:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
应用场景:
订单数据场景:订单每次状态的修改涉及到记录日志、发送通知等固定的操作,那么每个状态的日志记录就可以在其子类实现。
再举个不恰当的比喻:早晨上班,定义一些步骤,“交通方式” -> “到公司吃早餐” -> “开始工作”。其中具体的实现由子类完成,比如有的人住公司附近,那就是走路上班,离的远开车上班,都属于不同的交通方式。
优点:
角色:
AbstractTemplate:抽象模板类。定义算法的骨架。
TemplateA:模板实现类。子类重写其方法。
代码示例:
//抽象模板类
public abstract class AbstractTemplate {
protected abstract void notice(Object params);
protected abstract void log(Object params);
protected abstract void store(Object params);
//定义算法的骨架
public void build(Object params) {
notice(params);
log(params);
store(params);
}
}
//具体子类实现
public class TemplateA extends AbstractTemplate {
@Override
protected void notice(Object params) {
System.out.println("企业微信通知");
}
@Override
protected void log(Object params) {
System.out.println("企业微信日志");
}
@Override
protected void store(Object params) {
System.out.println("存储");
}
}
public class TemplateB extends AbstractTemplate {
@Override
protected void notice(Object params) {
System.out.println("公众号通知");
}
@Override
protected void log(Object params) {
System.out.println("公众号日志");
}
@Override
protected void store(Object params) {
System.out.println("存储");
}
}
public class TemplateMethodPattern {
public static void main(String[] args) {
Object params = "";
AbstractTemplate template = new TemplateA();
template.build(params);
}
}
背景:
由于公司业务的需求,一条数据从上传到完成。中间有很多个状态步骤
,比如数据的新建状态、上传状态、接收状态、审核状态等等。
每条数据状态的变更除了更新db之外,有的状态还需要通知企业微信、通知公众号等等,这是它们之间的相同点(通知)
。
最开始状态只有几种时,采用if else来完成简单判断逻辑。随着需求的迭代,状态越来越多,方法从最开始的几行到后来的上百行。
所以抽空采用设计模式来重构
这个状态的变更逻辑。
伪代码,命名和业务逻辑已修改,其它和真实场景一致。
@RestController
@RequestMapping("/state")
@Slf4j
public class StateController {
@ApiOperation(value = "更新数据状态")
@PostMapping(value = "/update")
public Object update(@RequestBody StateDo stateDo) {
if (stateDo.getState() == 1) {
//处理对应逻辑
}
else if (stateDo.getState() == 2) {
//处理对应逻辑
}
else if (stateDo.getState() == 3) {
//处理对应逻辑
}
...
return Result.success();
}
}
可以看到前期用if else判断状态处理对应的逻辑非常简单,新增一个状态就加一个else。随着状态越来越多,if else会变得复杂和难以维护。
按照模板模式的角色定义,我们先写模板抽象类:
/**
* @description: 抽象模板类
*/
@Slf4j
public abstract class AbstractState {
//通过 ConcurrentHashMap(线程安全) 实现单例注册表
protected static final Map STATE_BEAN_MAP = new ConcurrentHashMap<>(16);
public static AbstractState getStrategy(StateDo stateDo) {
//策略模式,根据客户端传过来的状态选择对应的策略实现类执行
int status = stateDo.getState() != null ?
stateDo.getState() : Stateless.STATE_LESS;
//如果未匹配到状态类,则走默认的实现
AbstractState target = STATE_BEAN_MAP.getOrDefault(status,
STATE_BEAN_MAP.get(Stateless.STATE_LESS));
target.build(stateDo);
return target;
}
//模板模式
public void build(StateDo stateDo) {
//前置校验,父类统一校验
preCheck(stateDo);
//子类运行逻辑,必须重写实现
process(stateDo);
//通知,子类有需要可重写
notice(stateDo);
//日志,子类有需要可重写
log(stateDo);
}
protected void preCheck(StateDo stateDo) {
//前置校验, 抽象父类中校验即可
}
protected abstract void process(StateDo stateDo);
protected abstract int getStatus();
protected void notice(StateDo stateDo) {}
protected void log(StateDo stateDo) {}
/**
* 业务状态后置处理器
*/
private static class StatePostProcessor implements Ordered, BeanPostProcessor, BeanFactoryPostProcessor, ApplicationContextInitializer {
//BeanFactoryPostProcessor 容器初始化之后触发,bean实例化之前执行
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinitionRegistry beanRegistry = (BeanDefinitionRegistry)beanFactory;
Reflections reflections = new Reflections("com.java.pattern");
//扫描抽象类的子类
Set> subTypes = reflections.getSubTypesOf(AbstractState.class);
for (Class clazz : subTypes) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
//将 Bean定义成BeanDefinition
beanDefinition.setBeanClass(clazz);
String shortClassName = ClassUtils.getShortName(clazz);
//首字母大写转小写
String beanName = Introspector.decapitalize(shortClassName);
//将bean注入到spring 容器中,统一管理
beanRegistry.registerBeanDefinition(beanName, beanDefinition);
beanFactory.addBeanPostProcessor(new StatePostProcessor());
}
}
@Override
public int getOrder() {
//控制执行顺序
return Ordered.LOWEST_PRECEDENCE;
}
/**
* 容器刷新之前初始化
* @param context
*/
@Override
public void initialize(ConfigurableApplicationContext context) {
//此项目由spring boot启动,有引用spring cloud相关组件,所以项目启动时除了加载spring boot上下文,还会加载spring cloud父上下文。
//那么initialize方法就会执行两次。
//后面的refreshArgs属性源判断是为了兼容nacos刷新上下文逻辑。
if (context.getParent() != null && context.getEnvironment().getPropertySources().get("refreshArgs") == null) {
context.addBeanFactoryPostProcessor(new StatePostProcessor());
}
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
//实例化、依赖注入、初始化完毕时执行
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof AbstractState) {
//将对象添加到单例注册表
STATE_BEAN_MAP.put(((AbstractState) bean).getStatus(), (AbstractState) bean);
}
return bean;
}
}
}
模板抽象类这么长的主要原因是包含了一个静态内部类StatePostProcessor。
StatePostProcessor为业务后置处理器,主要作用是容器初始化后扫描其模板抽象类的子类,注入到spring 容器中。待对应的bean实例化后。最终会将其添加到单例注册表中。
接口请求时根据对应的策略状态选择策略类执行逻辑。
这里提一嘴bean初始化和实例化区别
初始化:类初始化只会执行一次,因为class类只被加载一次。会执行static block。
实例化:new 对象或调用Class 对象的newInstance方法,叫做实例化。
后置处理器这块涉及到spring的一些知识。可见我之前的文章:Spring boot启动流程分析精简版
模板实现类:
/**
* @description: 无状态处理类
*/
public class Stateless extends AbstractState {
/**
* 无状态,约定默认值-1
*/
public static final int STATE_LESS = -1;
@Override
protected int getStatus() {
return STATE_LESS;
}
@Override
protected void process(StateDo stateDo) {
//数据存储
}
}
/**
* @description: 上传态
*/
public class UploadState extends AbstractState {
@Override
protected int getStatus() {
return 1;
}
@Override
protected void process(StateDo stateDo) {
//数据更新
}
@Override
protected void notice(StateDo stateDo) {
//需要通知公众号
}
@Override
protected void log(StateDo stateDo) {
//需要记录定制化日志
}
}
/**
* @description: 通过态
*/
public class PassState extends AbstractState {
@Override
protected int getStatus() {
return 2;
}
@Override
protected void process(StateDo stateDo) {
//数据更新、处理业务逻辑
}
@Override
protected void notice(StateDo stateDo) {
//通知企业微信
}
@Override
protected void log(StateDo stateDo) {
//记录通过态相关定制化日志
}
}
调用方式:
@RestController
@RequestMapping("/state")
@Slf4j
public class StateController {
@ApiOperation(value = "更新数据状态")
@PostMapping(value = "/update")
public Object update(@RequestBody StateDo stateDo) {
//获取策略子类执行
AbstractState.getStrategy(stateDo);
return Result.success();
}
}
策略模式和状态模式结构类似。
主要区别:
策略的选择由客户端决定,自己不能修改,比如根据传入来的参数选择支付宝支付,还是微信支付。
状态模式中,能修改自己的状态,向下一个状态转移。
采用设计模式重构后,可以看到入口处代码简洁。抽象父类里封装了固定不变的代码流程。子类重写实现其逻辑。每个状态类的职责更加清晰。
设计模式简介 | 菜鸟教程