系统整理 河北王校长的 贯穿设计模式 和 王争的设计模式之美,希望能形成肌肉记忆
文章目录
- 为什么需要掌握设计模式
- 1. 六大原则
- 介绍
- 1. 单一职责原则
- 2. 开闭原则
- 3. 里式替换原则
- 4. 依赖倒置原则
- 5. 接口隔离原则
- 6. 迪米特法则
- 分类
- 单例模式
- 适配器模式
- 封装有缺陷的接口设计
- 统一多个类的接口设计
- 替换外部依赖A为外部依赖B
- 兼容老版本的接口
- 适配不同格式的数据
- 不同日志框架的适配
- 桥接模式
- implementor的实现
- abstract的实现
- 搭建桥梁
- 调用
- 补充
- 组合模式
- 解释
- 实现
- 对应controller service和repository
- 访问者模式
- 概念
- Visitor
- ConcreteVistor
- ObjectStructure数据提供者
- controller service和repository
- 状态模式
- 概念
- 采用spring状态机
- 概念
- 具体实现
- 观察者模式
- 基于状态机的观察者模式
- controller、service
- 雷点
- 命令模式
- 概念
- 实现
- 接收者OrderCommandReceiver
- 抽象命令OrderCommandInterface
- 具体命令OrderCommand
- 命令调用者Invoker
- 调用
- 策略模式
- 概念
- 实现
- 抽象策略类PayStrategyInterface
- 具体策略AlipayStrategy
- context
- 门面模式
- 解释
- 实现
- 工厂模式
- 概念
- 实现
- 享元模式
- 解释
- 责任链
- 解释
- 实现
- 需求描述
- 实现
- 装饰器模式
- 解释
- 实现
- 需求
- 实现
- 建造者模式
- 概念
- 实现
- 需求分析
- 实现
- 原型模式
- 概念
- 实现
- 中介者模式
- 概念
- 实现
- 需求分析
- 实现
- Mediator抽象中介者AbstractMediator
- Colleague抽象同事类AbstractCustomer
- ConcreteColleague具体同事类Buyer和Payer
- ConcreteMediator具体中介者Mediator
- 保证全局唯一中介者
- 模板方法模式
- 概念
- 实现
- 需求
- 实现
为什么需要掌握设计模式
类比于我们的成语、行话,助力我们的大脑快速高效的沟通和研读优秀的代码
1. 六大原则
介绍
1. 单一职责原则
Single Responsibility Principle, SRP,一个类只负责一个功能领域的相应职责。也就是我们常说的“高内聚,低耦合”
2. 开闭原则
Open-Closed Principle,OCP:对扩展开放,对修改关闭
也就是尽量在不修改原有代码的情况下进行扩展
3. 里式替换原则
Liskov Substitution Principle,LSP:所有引用父类的地方必须能透明的使用其子类的对象。
在程序中尽量使用基类类型来对对象进行定义,在运行时再确定子类类型,用子类对象来替换父类对象。
算是实现开闭原则的重要方式之一,通俗的说:子类可以扩展父类的功能,但不能改变父类原有的功能
4. 依赖倒置原则
Dependency Inversion Principle,DIP:抽象不应该依赖于细节,细节应该依赖于抽象,也就是面向接口编程,而不是针对实现编程。
开闭原则是目标,里式替换是基础,依赖倒置是手段。
感觉和spring的DI有点联系,后续再思考。
5. 接口隔离原则
Interface Segregation Principle,ISP:使用多个专门的接口,而不适用单一的总接口,即客户端不应该依赖那些它不需要的接口
每个接口应该承担相对独立的角色,提供定制服务,当然接口也不能太小,灵活性会变差。控制好接口的粒度。
6. 迪米特法则
Law of Demeter,LoD, 也叫最少知识原则,LeastKnowledge Principle,LKP:一个软件实体应当尽可能少的与其他实体发生相互作用
也就是解耦合,降低系统的耦合度。
分类
大致按照设计模式的应用目标分类,分为创建型、结构型和行为型
- 创建型模式,是对对象创建过程的各种问题和解决方案的总结,包括各种工厂模式(Factory, Abstract Factory)、单例模式(Singleton)、构建者模式(Builder)、原型模式(Prototype)
- 结构型模式,是对软件设计结构的总结,专注于类、对象继承、组合方式的实践经验。常见的有桥接模式(Bridge)、适配器模式(Adapter)、装饰器模式(Decorator)、代理模式(Proxy)、组合模式(Composite)、门面模式(Facade)、享元模式(Flyweight)等。
- 行为型模式,是从类或者对象之间交互、职责划分等角度总结的模式,常见有策略模式(Strategy)、解释器模式(Interpreter)、命令模式(Command)、观察者模式(Observer)、迭代器模式(Iterator)、模板方法模式(Template Method)、访问者模式(Visitor)
单例模式
之前总结有,https://blog.csdn.net/wjl31802/article/details/91360815
单例模式最常见,必须掌握。spring默认就是单例模式
适配器模式
封装有缺陷的接口设计
可以看作一种补偿模式,也就是设计初期未考虑到一些接口,比如登录没考虑到第三方登录,这时候可以用。
具体类图如下
实现的效果如下
适配器(Adapter)角色,既能够支持已有功能(用户名/密码登录),也能够适配扩展功能(第三方账号授权登录),适配的扩展功能还能够复用已有的方法(register方法和login方法)
具体gitee的实现详见
设计模式-适配器模式-通过gitee登录应用系统
统一多个类的接口设计
某个功能的实现依赖多个外部系统(或者说类),敏感词过滤为例,系统可能依赖多个敏感词库,提高过滤的召回率。但是他们的接口实现都不太一样,通过适配器把接口适配为统一的接口定义,保证复用性。
替换外部依赖A为外部依赖B
兼容老版本的接口
版本升级后,部分接口被弃用,标注deprecated,但是仍然有部分项目在用,如何丝滑的过渡?
jdk给我们的demo,jdk1.0的容器遍历的类Enumeration,在JDK1.2的时候改为Iterator类,那如何兼容呢?采用适配器模式,暂时保留 Enumeration 类,并将其实现替换为直接调用 Itertor
适配不同格式的数据
不同格式的数据之间的适配,比如,把从不同征信系统拉取的不同格式的征信数据,统一为相同的格式,以方 便存储和使用。再比如,Java 中的 Arrays.asList() 也可以看作一种数据适配器,将数组类 型的数据转化为集合容器类型。
不同日志框架的适配
2023年爆出的log4j的漏洞让全国人民都知道了这个日志框架。
java的日志框架比较混乱,或者说多元,因为最开始没有和数据库连接一样制定相关的接口规范。
梳理下大致的发展:
- 最开始只有
System.out.print
这种,没有日志级别啥的 - 2001年log4j诞生,定义了Logger Appender Level等概念,但是同步锁导致高并发下的性能有问题
- sun公司jdk1.4也搞了jul包
java.util.logging
但是性能和功能都一般 - 为了适配这两个,搞了个接口,JCLL(Jakarta Commons Logging),在ClassLoader里面找log4j,没有就用JUL,但是性能太差
- log4j的作者写了个slf4j接口,为了追求性能,又搞了logback,完全兼容
- 那log4j由于性能问题,Apache宣布2015年不再维护,就又搞了log4j2,但是它跟log4j不兼容
那问题来了,如果不同的项目,又用到logback,又用到log4j,然后呢想用slf4j,怎么整?
就考虑到适配器模式,把不同的框架接口二次封装,适配为统一的slf4j接口定义。
桥接模式
桥接模式也叫作桥梁模式(Bridge Pattern),该模式旨在将抽象和实现解耦
还是以三方登录为例子,核心逻辑的实现需要在“右路Implementor的结构体系中
要实现什么功能?
- 实现三方登录
- 维持现有的注册登录
implementor的实现
public interface RegisterLoginFuncInterface {
public String login(String account,String password);
public String register(UserInfo userInfo);
public boolean checkUserExist(String userName);
public String login3rd(HttpServletRequest request);
}
然后就是具体的实现,这里面就有问题了,比如RegisterLoginByDefault是否要实现login3rd?RegisterLoginByGitee是否要实现login? 通过抽象层解决
abstract的实现
需要提供给client这些方法:login抽象方法、register抽象方法、checkUserExists抽象方法和第三方账号登录的login3rd抽象方法
public abstract class AbstractRegisterLoginComponent {
// 桥梁
protected RegisterLoginFuncInterface funcInterface;
public AbstractRegisterLoginComponent(RegisterLoginFuncInterface funcInterface) {
validate(funcInterface);
this.funcInterface = funcInterface;
}
protected final void validate(RegisterLoginFuncInterface funcInterface) {
if (!(funcInterface instanceof RegisterLoginFuncInterface)) {
throw new UnsupportedOperationException("unknown register/login function type!");
}
}
public abstract String login(String username, String password);
public abstract String register(UserInfo userInfo);
public abstract boolean checkUserExists(String userName);
public abstract String login3rd(HttpServletRequest request);
}
用“抽象和实现”两种类结构的设计换来了代码的高扩展性,换来了核心实现对Client端的“最少知识”原则,换来了耦合度的降低,就好比我们用空间换来了执行速度。
继续左路突破,RegisterLoginComponent子类
搭建桥梁
通过在AbstractRegisterLoginComponent抽象类中关联RegisterLoginFuncInterface接口,并以“构造函数”的形式,初始化RegisterLoginFuncInterface接口属性,完成抽象与实现的桥梁搭建。
调用
Controller层→Service层→桥接模式的左路的抽象AbstractRegisterLoginComponent入口
service层调用具体的登录实现,不能直接new,这样的话,每个进行login登录的用户线程,都会new两个对象,一个是左路的抽象子类RegisterLoginComponent作为调用入口;一个是右路具体的子类,如RegisterLoginByDefault。对于用户量庞大且活跃度较高的应用,这种代码很可能会引起频繁的垃圾收集操作。
引入工厂类RegisterLoginComponentFactory进行RegisterLoginComponent对象的生成和缓存。
利用@PostConstruct注解,在RegisterLoginByDefault对象和RegisterLoginByGitee对象注入到SpringBoot的容器后,初始化funcMap
补充
桥接模式的核心其实就是桥梁,本质也是组合模式,只是扩展性更强。
监控告警可以采用这种,Notification是抽象,MsgSender是实现,具体有短信告警、电话告警等
组合模式
解释
将对象组合成树形结构以表示“部分—整体”的层次结构
- Component抽象角色。所有树形结构的叶子节点和非叶子节点都需要继承该抽象角色
- Composite树枝构件角色 非叶子节点
- Leaf叶子构件角色
但是有些过时,现实场景,不能保证叶子节点永远不会没有子节点。因此再继续优化。
实现
设计要点:
- addProductIteam方法和delProductIteam方法,都不强制子类进行实现,因此没有使用abstract修饰这两个方法,子类可以根据需求自主选择实现哪些方法。
- 参数都用AbstractProductIteam本身,遵循李氏替换原则
对应controller service和repository
其中,generateProductTree方法是将数据库的商品类目信息转化成组合模式树形结构的核心代码
访问者模式
概念
旨在操作某对象结构中各个(各层级)元素的模式,在不改变元素整体结构的前提下,定义作用于这些元素的新操作。
商品类目的树形结构来说,我们可以利用访问者模式,对树形结构的任意节点进行操作(添加、删除)。
除Client调用端以外,访问者模式包含5个角色,其中两个角色属于组合模式,我仅仅需要搞定其他3个角色即可
- Visitor抽象访问者。接口或抽象类均可,定义访问者能够访问的数据类型
- ConcreteVistor具体访问者 定义支持商品类目添加的具体访问者和支持商品类目删除的具体访问者
- ObjectStructure 数据提供者 Client先通过ObjectStructure获取树形商品类目数据,再调用Visitor对第1步获取的树形商品类目数据进行访问操作
- Component被访问者抽象角色
- Composite被访问者具体角色
Visitor
public interface ItemVisitor<T> {
T visitor(AbstractProductItem productItem);
}
ConcreteVistor
ObjectStructure数据提供者
其实就是RedisCommonProcessor,从缓存获取数据
controller service和repository
无论是商品类目添加还是商品类目删除,代码的实现逻辑并未涉及补偿机制以及极端场景状态模式
概念
- State抽象状态角色:该角色主要进行状态的定义和方法的定义。
- ConcreteState具体状态类:不同的状态需要创建不同的状态类(订单有四个状态,就需要创建四个具体的状态类),并且实现抽象状态类定义的方法。
- Context上下文角色(也称环境角色):封装状态的转化逻辑,是状态的转化过程的容器,暴露给客户端使用。可以类比Spring框架的ApplicationContext角色。
缺点:
- 使得service层形同虚设,逻辑全在context中
- 并发的问题,不同的订单不能使用同一个context,context有状态。但是无状态的话,违背UML类图的结构
解决:
采用spring状态机
概念
状态机是状态模式的一种应用,相当于上下文角色的一个升级版,在工作流状态转化、订单状态转化等各种系统中有大量使用,封装状态的变化规则。
- state(状态):如订单的不同状态
- transition(转移):一个状态接收一个输入,执行了某些动作到达了另外一个状态的过程就是一个transition(转移)。例如,订单状态在ORDER_WAIT_PAY的情况下,接收到了一个支付动作,那么订单状态就会从ORDER_WAIT_PAY状态transition(转移)到ORDER_WAIT_SEND状态。
- transition condition (转移条件):也叫作Event(事件),在某一状态下,只有达到了transition condition(转移条件),才会按照状态机的转移流程转移到下一状态,并执行相应的动作。例如,订单状态在ORDER_WAIT_PAY的情况下,接收到了一个支付动作,并且支付成功了,此处的支付成功就是transition condition(转移条件)。
- action (动作):在状态机的运转过程中会有很多种动作,此处的动作是一个统称,如进入动作—在进入状态时、退出动作—在退出状态时、输入动作—依赖于当前状态和输入条件进行、转移动作—在进行特定转移时进行
基于Spring状态机进行实战需要创建的类:
- 订单对象类
- Context上下文环境类 不需要
- 代表订单状态的枚举类(enum),四个订单状态
- 代表订单操作的枚举类(enum)
- 转化过程配置到Spring状态机中,需要创建基于@Configuration注解的配置类
具体实现
创建订单状态枚举类
public enum OrderState {
ORDER_WAIT_PAY, //待支付
ORDER_WAIT_SEND, // 待发货
ORDER_WAIT_RECEIVE, // 待收货
ORDER_FINISH; // 完成订单
}
订单操作枚举类
public enum OrderStateChangeAction {
PAY_ORDER, // 支付操作
SEND_ORDER, // 发货操作
RECEIVE_ORDER; //收货操作
}
订单对象类
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Order {
private String orderId;
private String productId;
private OrderState orderState;
private Float price;
}
状态机配置类OrderStateMachineConfig
- 设置初始状态
- 配置转换流程
- 状态机自身的存储和读取 spring-statemachine-redis
观察者模式
核心就两个:观察者observer和被观察者subject,再加上各自的子类
缺点:很难优雅的把观察者添加到observerList中
解决:对observersList属性的初始化过程中,我们需要引入一个用fianl static修饰的Vector数据结构,并通过@PostConstruct注解初始化Vector中的元素
基于状态机的观察者模式
作用:
- 监听器 监听对象
- 监听到状态变化,所采取的行动逻辑
通过@OnTransition注解,标注source和target属性,代表了该方法专门用于监听订单状态从ORDER_WAIT_PAY到ORDER_WAIT_SEND的转化
PayToSend方法的参数Message<OrderStateChangeAction>message
,此处使用org.springframework.messaging.Message类,对OrderStateChangeAction订单操作枚举类进行了封装,从字面意义来说,这个参数代表了一条订单操作的消息。我们可以根据这个消息,获取订单的信息,并进行后续处理
controller、service
雷点
所有的第三方平台的支付操作,都是两段式的:第一段向第三方支付平台提交订单支付请求;第二段第三方支付平台回调我们平台的接口。只有在第二段的时候,我们才能确认订单支付是否成功,才能确认是否需要将订单状态转为待发货状态。
其他注意:
- 订单存储到Redis中,是以orderId作为key的。创建完订单后,可以打开Redis客户端,通过get具体的orderId命令查看当前存储的订单信息。当订单签收成功后,Redis中的订单信息就会被删除
- 状态机存储到Redis中,是以具体的orderId+STATE为key的
命令模式
概念
将请求封装到一个命令(Command)对象中,实现了请求调用者和具体实现者之间的解耦
- 抽象命令(Command)角色 一般定义为接口,用于定义执行命令的接口
- 具体命令(ConcreteCommand)角色 与命令接收者进行关联,调用命令接收者的方法
- 接收者(Receiver)角色 真正执行命令的对象。订单转化流程的相关逻辑,都在此处进行实现。接收者可以有多个,主要根据业务需求而定
- 调用者(Invoker)角色 接收客户端正确的命令,并触发命令的执行
- 客户端(Client)角色 创建Invoker和命令,并通过invoker触发命令
实现
接收者OrderCommandReceiver
@Component
public class OrderCommandReceiver {
// 接收命令后执行
public void action(Order order) {
switch (order.getOrderState()) {
case ORDER_WAIT_PAY:
System.out.println("创建订单:order = " + order);
System.out.println("存入db");
return;
case ORDER_WAIT_SEND:
System.out.println("支付订单:order = " + order);
System.out.println("存入db");
System.out.println("通过 MQ 通知财务");
System.out.println("通过 MQ 通知物流");
return;
case ORDER_WAIT_RECEIVE:
System.out.println("订单发货:order = " + order);
System.out.println("存入db");
return;
case ORDER_FINISH:
System.out.println("接收订单:order = " + order);
System.out.println("存入db");
return;
default:
throw new UnsupportedOperationException("Order state error");
}
}
}
抽象命令OrderCommandInterface
public interface OrderCommandInterface {
void execute(Order order);
}
具体命令OrderCommand
@Component
public class OrderCommand implements OrderCommandInterface{
@Autowired
private OrderCommandReceiver receiver;
@Override
public void execute(Order order) {
this.receiver.action(order);
}
}
命令调用者Invoker
public class OrderCommandInvoker {
public void invoke(OrderCommandInterface command, Order order) {
command.execute(order);
}
}
调用
OrderStateListener类的调用
详见之前的观察者模式的OrderStateListener
OrderCommandInvoker invoker = new OrderCommandInvoker();
invoker.invoke(orderCommand,order);
service的调用,详见 [[设计模式重新整理#controller、service]]
策略模式
概念
指对象有某个行为,但是在不同的场景中,该行为有不同的实现逻辑,即不同的策略,实现方式不同
哈哈,看着跟状态模式一样,
- Strategy抽象策略角色:该角色主要进行策略方法的定义
- ConcreteStrategy具体策略类:不同的策略需要创建不同的策略类(在多种类支付的实战中,支付宝支付策略和微信支付策略就是具体的策略类),并且实现抽象策略类定义的方法。
- Context上下文角色(也称环境角色):关联抽象策略类,并调用策略类的方法
实现
第三方支付,如支付宝
抽象策略类PayStrategyInterface
public interface PayStrategyInterface {
// 返回值类型string 访问第三方支付,平台返回一个url地址,让用户进行支付
String pay(Order order);
}
具体策略AlipayStrategy
context
Service层调用的是门面模式的封装层,门面模式调用策略工厂及策略模式的相关类
策略模式和策略工厂的配合使用,就不需要将context修改成无状态类
public abstract class AbstractPayContext {
public abstract String execute(Order order);
}
public class PayContext extends AbstractPayContext{
private PayStrategyInterface payStrategy;
public PayContext(PayStrategyInterface payStrategy) {
this.payStrategy = payStrategy;
}
@Override
public String execute(Order order) {
return this.payStrategy.pay(order);
}
}
门面模式
解释
旨在封装。可以封装一个需求模块,如多种类支付模块,也可以封装一个复杂的系统,从设计模式的角度看Spring Cloud Gateway,Gateway就是一个门面,Spring Cloud Gateway提供了所有请求的访问入口,为后续无数的微服务模块提供了门面。
- Facade门面角色:该角色暴露给调用者进行调用。作为门面角色,它知道子系统的所有功能,门面角色会将客户端发来的请求转发到子系统中,转发之前,可以进行一定的类型转换和参数封装等辅助逻辑
- subsystem子系统角色:子系统角色可以简单到一个类,也可以复杂到多个系统。当然,针对第三方支付需求来说,子系统角色就是策略模式的Context环境类——PayContext
实现
facade可以直接new创建Context类和具体的策略类,但是每次支付都创建,对内存产生很大的压力,导致频繁的minorGC
工厂模式
概念
- Product:抽象产品角色
- ConcreteProduct:具体产品角色。此处我们具体需要创建的对象是PayContext类
- Creator:抽象工厂角色
- ConcreteCreator:具体工厂角色
实现
享元模式
解释
旨在解决重复对象的内存浪费问题,为重复的对象创建缓冲池。享元模式最经典的就是池技术,如String常量池、数据库连接池等都是享元模式的经典应用。我们对享元模式的使用,就是通过Map作为本地缓存,减少PayContext对象的重复创建。
- FlyWeight角色:抽象享元角色。定义对象的抽象方法和属性,对应我们的AbstractPayContext类
- ConcreteFlyweight角色:具体享元角色。对应我们的PayContext类
- FlyWeightFactory角色:享元工厂。提供对象的创建、缓存及获取,对应我们的PayContexFactory类
- Client角色:3个箭头指向,意思是Client既能调用FlyWeightFactory享元工厂角色,又能直接调用具体的实现类,对应我们的PayFacaded类
- UnsharedFlyweight角色:不可共享的角色,示例中没有
Spring IOC就是一个大型享元模式的应用,里边就有UnsharedFlyweight角色
- FlyweightFacotry Spring的Bean Factory,就是一个享元工厂,BeanFactory就是采用的ConcurrentHashMap进行单例对象的缓存,减少对象的创建
- Flyweight 使用@Controller、@Service、@Component等注解标注我们的类,Spring会根据我们标注的注解进行检索,找到标注注解的具体的类,将对象托管到Spring的容器中。诸如@Controller、@Service、@Component等注解,都是依托于java.lang.annotation包实现的。因此,可以将
java.lang.annotation.*
视为抽象享元角色 - ConcreteFlyweight 通过@Autowired注解将对象注入到我们的类中,全局只有一个对象,完全共享,不会对内存产生压力
- UnsharedFlyweight 对于一些对象,我们无法将其设置为单例对象,每次使用该对象,我们需要使用new关键字进行创建,或者我们可以使用@Scope注解,将对象标记为多例prototype对象,在每次使用对象时,都会重新创建一个全新的对象
- Client调用者 可以通过Spring的上下文对象ApplicationContext,调用BeanFactory的getBean方法获取单例对象;可以直接通过new关键字创建具体对象,即使对象是单例的,我们依然可以通过new关键字创建对象;可以直接通过new关键字创建多例对象
责任链
解释
- Handler抽象责任角色:抽象责任角色负责定义抽象方法,并且需要在该角色中定义nextHandler。开篇我们就提到过,责任链模式就类似于链表,创建链表节点时,我们会定义next节点属性。同理,此处我们定义nextHandler属性,将多个责任类定义到一个责任处理链条中
- ConcreteHandler具体责任角色:具体的责任类。根据本章的实战需求,我们需要定义三个具体责任类:按用户购物种类筛选的责任类、按用户所在城市筛选的责任类和按用户性别筛选的责任类
- Client责任链的装配者、使用者:Client在进行责任链的调用前,需要对责任链条进行装配,说直白些,就是将多个具体责任类,通过nextHandler属性进行连接,因此Client角色的作用分为两个:装配责任链条、调用责任链条的头节点
实现
需求描述
业务投放场景
不同的用户可能会收到不同的投放内容,我们需要根据用户不同的属性进行投放业务的筛选,只展示符合当前用户的投放信息
返回信息之前,增加点判断条件对信息进行筛选
业务那边要求实时调整筛选条件
实时调整代表业务部门可以根据需求随意组合筛选条件。举个例子,我们的代码中定义了“按用户购物种类的筛选逻辑”“按用户所在城市的筛选逻辑”和“按用户性别的筛选逻辑”,如果业务部门想要将某条投放信息展示给所有女性用户,业务部门只需要使用“按用户性别的筛选逻辑”;那如果业务部门想要将某条信息投放给所有的居住在北京的女性用户,业务部门就需要使用“按用户性别的筛选逻辑”和“按用户所在城市的筛选逻辑”。
一些广告商想要作投放推广,投放全国女性用户和投放北京女性用户的价格肯定是不一样的啊。咱们目前只是入门级别的,把城市作为最小的投放单元。微信的广告投放更狠,都是按照方圆公里数进行投放的,投放多少公里以内的符合某些条件的用户。
Apollo是携程框架部门研发的开源配置管理中心,能够集中化管理应用的不同环境、不同集群的配置,配置修改后能够实时推送到应用端,而且Apollo还有UI操作界面,完全满足投放的业务需求。业务部门可以在Apollo的UI界面修改筛选条件,然后单击发布,新的刷选条件就会实时地推送到咱们的项目
Apollo配置中心+责任链模式。筛选条件目前只需要支持三个:“按用户购物种类的筛选逻辑”“按用户所在城市的筛选逻辑”和“按用户性别的筛选逻辑
实现
需求:
- 在Apollo中配置的duty.chain的值是“city,sex,product”,我们如何将它们转化成CityHandler→SexHandler→ProductHandler这样的责任对象链条
- 如何确保,只有在第一次初始化责任链条或者是duty.chain的配置发生改变时,才去重新进行责任链条的组装
采用了枚举类+反射责任类创建+synchronized同步代码块+链表哑结点的简易算法进行实现,并且使用了两个全局变量控制组装时机,只有在第一次进行责任链条初始化和duty.chain配置更新时,才会触发责任链条的重新组装
责任类
组装和调用
装饰器模式
解释
- Component抽象构件:Component是一个接口或者抽象类,定义我们核心的原始对象。本章实战,我们需要对OrderService的pay方法进行装饰,因此此处的Componet抽象构件,就是OrderService的抽象父类
- ConcreteComponent具体构件:你要装饰的就是它,就是我们的OrderService类
- Decorator抽象装饰角色:定义装饰器的属性和新的方法。此处我们的积分更新/红包发放方法的定义,就是在该类中进行的
- ConcreteDecorator具体装饰角色:新的装饰功能的具体实现类。具体实现积分更新/红包发放逻辑,服务降级逻辑也在此处
实现
需求
平台积分更新和红包发放,在支付之后,并且需要做服务降级
- 0代表正常服务,每次商品支付成功后,直接进行更新操作
- 1代表延迟服务,遇上秒杀或双11活动,需要进行延迟更新,比如说凌晨0点开始秒杀,可以将积分更新或红包发放在30分钟后进行处理
- 2代表暂停服务,平台可以随时将积分更新和红包发放业务暂停
实现
apollo设置延时时间+rabbitmqTTL机制和死信队列实现延迟服务+装饰器隔离支付和积分红包的服务
![!\[\[Pasted image 20240909173849.png\]\]](https://i-blog.csdnimg.cn/direct/5bc2d79d1c094b9a98a8bdd0811b3f5f.png)建造者模式
概念
- Product产品类:要建造的对象,本章对应我们的电子发票类
- Builder抽象建造者:负责产品的组建,定义产品组建的抽象方法
- ConcreteBuilder具体建造者:实现抽象类定义的所有方法,并且返回一个组建好的对象。根据本章需求,返回电子发票对象
- Director导演类:负责指导Builder进行对象创建,也可以称之为指挥者
实现
需求分析
开发票,分为个人和企业
个人电子发票只需要填写抬头,企业电子发票需要填写税号、抬头还有银行卡等信息
使用建造者模式可以。
扩展性呢?
指挥者角色,这个角色主要负责信息的拼装,一般不负责信息的校验和获取。比如,将银行卡信息校验放到指挥者角色中不太合适,产品信息的获取放到这里也不合适,为指挥者角色做一个代理类,代理类中会有这些辅助的校验和辅助信息。后续新增其他逻辑的时候,直接在代理类中作修改就行,非常方便,扩展性很不错
此外,支持clone
两个意义:
一是对于发票的不可变信息,我们可以直接克隆,减少new关键字的使用,因为发票信息中,有一部分信息是固定不变的;
二是考虑未知的备份操作,一张电子发票开具后,可能不同的部门有不同的使用方式,支持Clone的话,可以直接在生成好的电子发票对象上进行Clone操作,生成两个相同的电子发票对象,分别进行不同的使用
实现
product产品类
builder建造者
director导演
director搞了个AbstractDirector抽象类,为了程序的扩展性,银行卡信息的校验放在代理类,此外后续可能有其他的校验
上面也是代理模式
- Subject抽象主题角色:被代理角色的抽象角色。拿本章需求来说,AbstractDirector类便是抽象主题角色
- RealSubject具体主题角色:被代理角色。也就是我们的Director类
- Proxy代理角色:在该角色中,我们要代理Director类的方法,并且增加前置的校验处理
原型模式
概念
- Prototype原型抽象角色:原型模式的抽象角色,就是Java自带的java.lang.Cloneable接口,我们无须自主创建该角色
- ConcretePrototype原型具体角色:实现Cloneable接口的对象。依照本章的实战需求,我们需要让PersonalTicket和CompanyTicket实现Cloneable接口。
实现
上个发票实现cloneable就是原型模式
这里涉及到 引用拷贝、深拷贝和浅拷贝的区别
引用拷贝:
创建了teacherWang对象,然后创建teacherSun对象等于teacherWang对象,teacherWang和teacherSun共用一个对象的内存地址,整个过程中,堆内存中只有一个Teacher对象
浅拷贝:
实现cloneable接口,只能拷贝最外层的对象。拿Teacher对象来说,通过浅拷贝,我们会在堆内存中创建新的Teacher对象,然而Teacher对象中的Student对象却不会被重新创建。
teacherSun和teacherWang指向不同的Teacher对象,但两个Teacher对象却指向同一个Student对象
深拷贝:
可以拷贝深层的student对象
如何实现?
- 让所有层次的对象,都实现Cloneable接口,并实现Object的Clone方法
- 通过Serializable序列化接口,实现深拷贝 大部分都是这种
中介者模式
概念
中介者模式可以通过中介者对象来封装一系列的对象交互,将对象间复杂的关系网状结构变成结构简单的以中介者为核心的星形结构,简化对象间的关系。同时,中介者模式能够将各个对象之间的关系解耦,每个对象不再与它关联的对象直接发生相互作用,而是通过中介者对象与关联的对象进行通信。
产品进销存系统中进行应用,包括我们的微信、QQ聊天,也都是采用了中介者模式的设计思想
- Mediator抽象中介者:定义不同同事之间的信息交互方法
- ConcreteMediator具体中介者:实现抽象中介者的方法,它需要知道所有的具体同事类,同时需要从具体的同事类那里接收信息,并且向其他具体的同事类发送信息。
- Colleague抽象同事类:以本章为例,该抽象角色是购买者和帮忙支付者的父类。
- ConcreteColleague具体同事类:每个具体同事类都只需要知道自己的行为即可,但是他们都需要认识中介者。以本章为例,购买者和帮忙支付者就是具体同事类,两个类都需要了解中介者类
跟代理模式的区别:
只负责中转,不处理具体事务
跟观察者模式的区别:
介入具体业务,对象的交互,eventbus属于观察者模式
缺点:
容易膨胀,变成大而复杂的上帝类
实现
需求分析
朋友代付
对于公司来说,咱们只关心商品的订单 订单谁支付、通过什么方式支付都不要紧,只要支付的时候,是当前的商品订单就行。
朋友代付功能,无非就是把商品订单信息转发到待支付的朋友那里,然后请朋友帮忙支付就行了
订单消息和支付结果中转的场景
- 订单消息中转 把我要购买的商品订单转发给我的朋友
- 支付结果中转 朋友付完钱,不得告诉你一声
实现
Mediator抽象中介者AbstractMediator
public abstract class AbstractMediator {
/**
* 消息交互
* @param orderId 用户的订单ID
* @param targetCustomer 目标用户,比如说张三请李四帮忙支付,那么李四便是目标用户
* @param customer 抽象同事类 中介者需要根据该参数确定调用者的角色——购买者或帮忙支付者
* @param payResult 只有支付成功后此参数才不为null
*/ public abstract void messageTransfer(String orderId, String targetCustomer, AbstractCustomer customer, String payResult);
}
Colleague抽象同事类AbstractCustomer
- 关联中介者,因为购买者和支付者都需要直接与中介者打交道,因此需要关联中介者类
- 定义与中介者进行消息交互的方法
- 有一些核心属性,如需要支付的订单号及当前客户信息等
public abstract class AbstractCustomer {
//关联中介者
public AbstractMediator mediator;
//订单ID
public String orderId;
//当前用户信息
public String customerName;
public AbstractCustomer(AbstractMediator mediator, String orderId, String customerName) {
this.mediator = mediator;
this.orderId = orderId;
this.customerName = customerName;
}
public String getCustomerName() {
return this.customerName;
}
// 和中介者的信息交互方法
public abstract void messageTransfer(String orderId, String targetCustomer, String payResult);
}
ConcreteColleague具体同事类Buyer和Payer
public class Buyer extends AbstractCustomer{
public Buyer(AbstractMediator mediator, String orderId, String customerName) {
super(mediator, orderId, customerName);
}
@Override
public void messageTransfer(String orderId, String targetCustomer, String payResult) {
super.mediator.messageTransfer(orderId,targetCustomer,this,payResult);
}
}
public class Payer extends AbstractCustomer{
public Payer(AbstractMediator mediator, String orderId, String customerName) {
super(mediator, orderId, customerName);
}
@Override
public void messageTransfer(String orderId, String targetCustomer, String payResult) {
super.mediator.messageTransfer(orderId,targetCustomer,this,payResult);
}
}
ConcreteMediator具体中介者Mediator
- 具体中介者需要知道所有的同事类,因此该类需要关联同事类
- 具体中介者需要实现抽象中介者的方法,作为不同同事类之间的信息交互桥梁
@Component
public class Mediator extends AbstractMediator{
public static Map<String, Map<String, AbstractCustomer>> customerInstances = new ConcurrentHashMap<>();
@Override
public void messageTransfer(String orderId, String targetCustomer, AbstractCustomer customer, String payResult) {
if (customer instanceof Buyer) {
AbstractCustomer buyer = customerInstances.get(orderId).get("buyer");
System.out.println("Friend pays on behalf: "+buyer.getCustomerName() + " transfer orderId "+ orderId+" to customer "+targetCustomer+" to pay");
} else if (customer instanceof Payer) {
AbstractCustomer payer = customerInstances.get(orderId).get("payer");
System.out.println("payment completed on behalf "+payer.getCustomerName()+" completed orderId "+orderId+" pay, notify "+targetCustomer+", pay result: "+payResult);
customerInstances.remove(orderId);
}
}
public void addInstance(String orderId, HashMap<String, AbstractCustomer> map) {
customerInstances.put(orderId, map);
}
}
保证全局唯一中介者
通过Map<String, Map<String, AbstractCustomer>>数据结构存储购买者和实际支付者的信息。其中,外层Map的key为OrderId,内层Map的key为Buyer和Payer,value为AbstractCustomer对象
代支付完成后,从Map中删除此orderId的信息
@Service
public class OrderService implements OrderServiceInterface{
@Autowired
private Mediator mediator;
public void friendPay(String sourceCustomer, String orderId, String targetCustomer, String payResult, String role) {
//创建中介者
Buyer buyer = new Buyer( mediator, orderId,sourceCustomer);
Payer payer = new Payer( mediator, orderId,sourceCustomer);
HashMap<String, AbstractCustomer> map = new HashMap<>();
map.put("buyer", buyer);
map.put("payer",payer);
mediator.addInstance(orderId,map);
if (role.equals("B")) {
buyer.messageTransfer(orderId,targetCustomer,payResult);
} else if (role.equals("P")) {
payer.messageTransfer(orderId,targetCustomer,payResult);
}
}
}
模板方法模式
概念
定义一个操作中的算法框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤
白话版:模板方法模式会定义好做一件事情的步骤,这个步骤是固定的,一部分步骤已经实现好了,你无须关心;另外一部分步骤没有做任何实现,需要子类进行实现
对于已经定义好的步骤,我们称之为基础方法,需要子类进行实现的方法称之为模板方法
- AbstractClass抽象模板对象:抽象模板对象主要负责基本方法的实现以及抽象模板方法的定义
- ConcreteClass具体模板对象:对抽象模板方法进行个性实现,不会改变整体的执行结构
Spring源码的核心refresh方法里面的postProcessBeanFactory方法和onRefresh方法就是两个模板方法,与我们距离最近的模板方法模式的使用案例,就是AQS源码中的使用。
实现
需求
审计日志
记录四种订单日志即可。每种订单日志有相似的部分,都有用户操作时间、操作类型、订单ID和用户ID
- 订单创建日志:无其他特殊信息,包含刚才描述的相似内容即可
- 订单支付日志:除相似内容外,还需要记录支付类型和实际支付金额
- 订单发货日志:除相似内容外,还需要记录快递公司和快递单编号
- 订单签收日志:无其他特殊信息,包含刚才所说的相似内容即可
这些日志放到哪里,数据处理部门给我们提供了一个Queue,我们直接将信息发送到Queue里就行。
实现
模型OrderAuditLog
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderAuditLog {
//当前用户信息
private String account;
// 用户操作
private String action;
// 用户操作具体时间
private Date date;
private String orderId;
// 其他额外信息
private Object details;
}
抽象类
public abstract class AbstractAuditLogProcessor {
//创建我们的auditLog 基础方法 不允许子类重写
private final OrderAuditLog basicAuditLog(String account, String action, String orderId) {
OrderAuditLog auditLog = new OrderAuditLog();
auditLog.setAccount(account);
auditLog.setAction(action);
auditLog.setOrderId(orderId);
auditLog.setDate(new Date());
return auditLog;
}
//定义抽象模板方法,设置订单审计日志的额外信息,供子类进行实现
protected abstract OrderAuditLog buildDetails(OrderAuditLog auditLog);
//定义订单审计日志的创建步骤 不允许子类重写
public final OrderAuditLog createAuditLog(String account, String action, String orderId) {
//设置审计日志的基本信息
OrderAuditLog auditLog = basicAuditLog(account, action, orderId);
// 设置额外信息
return buildDetails(auditLog);
}
}
具体实现如CreateOrderLog PayOrderLog 等过程
@Component
public class CreateOrderLog extends AbstractAuditLogProcessor{
@Override
protected OrderAuditLog buildDetails(OrderAuditLog auditLog) {
return auditLog;
}
}
调用
@Service
public class OrderService implements OrderServiceInterface{
@Autowired
private CreateOrderLog createOrderLog;
public Order createOrder(String productId) {
//订单生成的逻辑
String orderId = "OID" + productId;
Order order = Order.builder()
.orderId(orderId)
.productId(productId)
.orderState(OrderState.ORDER_WAIT_PAY)
.build();
redisCommonProcessor.set(orderId,order,900);
OrderCommandInvoker invoker = new OrderCommandInvoker();
invoker.invoke(orderCommand,order);
// 暂时 testAccount 后续可以从用户中心获取account
createOrderLog.createAuditLog("testAccount", "create", orderId);
return order;
}
}