915 lines
29 KiB
Markdown
915 lines
29 KiB
Markdown
## 设计模式
|
||
|
||
### 单例模式
|
||
|
||
#### 懒汉
|
||
|
||
```java
|
||
public class LazySingleton {
|
||
private static volatile LazySingleton instance;
|
||
|
||
private LazySingleton() {}
|
||
|
||
public static LazySingleton getInstance() {
|
||
if (instance == null) { // 第一次检查
|
||
synchronized (LazySingleton.class) {
|
||
if (instance == null) { // 第二次检查
|
||
instance = new LazySingleton();
|
||
}
|
||
}
|
||
}
|
||
return instance;
|
||
}
|
||
}
|
||
```
|
||
|
||
第一次检查:防止重复实例化、以及进行synchronized同步块。
|
||
|
||
第二次检查:防止有多个线程同时通过第一次检查,然后依次进入同步块后,创建N个实例。
|
||
|
||
volatile:防止指令重排序,instance = new LazySingleton(); 正确顺序是:
|
||
|
||
**1.分配内存**
|
||
|
||
**2.调用构造函数,初始化对象**
|
||
|
||
**3.把引用赋给 `instance`**
|
||
|
||
|
||
|
||
#### 饿汉
|
||
|
||
```java
|
||
public class EagerSingleton {
|
||
// 类加载时就初始化实例
|
||
private static final EagerSingleton INSTANCE = new EagerSingleton();
|
||
|
||
// 私有构造函数
|
||
private EagerSingleton() {
|
||
// 防止反射创建实例
|
||
if (INSTANCE != null) {
|
||
throw new IllegalStateException("Singleton already initialized");
|
||
}
|
||
}
|
||
|
||
// 全局访问点
|
||
public static EagerSingleton getInstance() {
|
||
return INSTANCE;
|
||
}
|
||
|
||
// 防止反序列化破坏单例
|
||
private Object readResolve() {
|
||
return INSTANCE;
|
||
}
|
||
}
|
||
|
||
```
|
||
|
||
|
||
|
||
### 工厂模式
|
||
|
||
#### 简单工厂
|
||
|
||
```java
|
||
// 产品接口
|
||
interface Product {
|
||
void use();
|
||
}
|
||
|
||
// 具体产品A
|
||
class ConcreteProductA implements Product {
|
||
@Override
|
||
public void use() {
|
||
System.out.println("使用产品A");
|
||
}
|
||
}
|
||
|
||
// 具体产品B
|
||
class ConcreteProductB implements Product {
|
||
@Override
|
||
public void use() {
|
||
System.out.println("使用产品B");
|
||
}
|
||
}
|
||
|
||
class SimpleFactory {
|
||
// 根据参数创建不同的产品
|
||
public static Product createProduct(String type) {
|
||
switch (type) {
|
||
case "A":
|
||
return new ConcreteProductA();
|
||
case "B":
|
||
return new ConcreteProductB();
|
||
default:
|
||
throw new IllegalArgumentException("未知产品类型");
|
||
}
|
||
}
|
||
}
|
||
|
||
public class Client {
|
||
public static void main(String[] args) {
|
||
// 通过工厂创建产品
|
||
Product productA = SimpleFactory.createProduct("A");
|
||
productA.use(); // 输出: 使用产品A
|
||
|
||
Product productB = SimpleFactory.createProduct("B");
|
||
productB.use(); // 输出: 使用产品B
|
||
}
|
||
}
|
||
```
|
||
|
||
缺点:添加新产品需要修改工厂类(违反开闭原则)
|
||
|
||
|
||
|
||
|
||
|
||
#### 抽象工厂
|
||
|
||
抽象工厂模式是一种创建型设计模式,它提供一个接口用于创建相关或依赖对象的家族,而不需要明确指定具体类。
|
||
|
||
```java
|
||
// 抽象产品接口
|
||
interface Button {
|
||
void render();
|
||
}
|
||
|
||
interface Checkbox {
|
||
void render();
|
||
}
|
||
|
||
// 具体产品实现 - Windows 风格
|
||
class WindowsButton implements Button {
|
||
@Override
|
||
public void render() {
|
||
System.out.println("渲染一个 Windows 风格的按钮");
|
||
}
|
||
}
|
||
|
||
class WindowsCheckbox implements Checkbox {
|
||
@Override
|
||
public void render() {
|
||
System.out.println("渲染一个 Windows 风格的复选框");
|
||
}
|
||
}
|
||
|
||
// 具体产品实现 - MacOS 风格
|
||
class MacOSButton implements Button {
|
||
@Override
|
||
public void render() {
|
||
System.out.println("渲染一个 MacOS 风格的按钮");
|
||
}
|
||
}
|
||
|
||
class MacOSCheckbox implements Checkbox {
|
||
@Override
|
||
public void render() {
|
||
System.out.println("渲染一个 MacOS 风格的复选框");
|
||
}
|
||
}
|
||
|
||
// 抽象工厂接口
|
||
interface GUIFactory {
|
||
Button createButton();
|
||
Checkbox createCheckbox();
|
||
}
|
||
|
||
// 具体工厂实现 - Windows
|
||
class WindowsFactory implements GUIFactory {
|
||
@Override
|
||
public Button createButton() {
|
||
return new WindowsButton();
|
||
}
|
||
|
||
@Override
|
||
public Checkbox createCheckbox() {
|
||
return new WindowsCheckbox();
|
||
}
|
||
}
|
||
|
||
// 具体工厂实现 - MacOS
|
||
class MacOSFactory implements GUIFactory {
|
||
@Override
|
||
public Button createButton() {
|
||
return new MacOSButton();
|
||
}
|
||
|
||
@Override
|
||
public Checkbox createCheckbox() {
|
||
return new MacOSCheckbox();
|
||
}
|
||
}
|
||
|
||
// 客户端代码
|
||
public class Application {
|
||
private Button button;
|
||
private Checkbox checkbox;
|
||
|
||
public Application(GUIFactory factory) {
|
||
button = factory.createButton();
|
||
checkbox = factory.createCheckbox();
|
||
}
|
||
|
||
public void render() {
|
||
button.render();
|
||
checkbox.render();
|
||
}
|
||
|
||
public static void main(String[] args) {
|
||
// 根据配置或环境选择工厂
|
||
GUIFactory factory;
|
||
|
||
String osName = System.getProperty("os.name").toLowerCase();
|
||
if (osName.contains("win")) {
|
||
factory = new WindowsFactory();
|
||
} else {
|
||
factory = new MacOSFactory();
|
||
}
|
||
|
||
Application app = new Application(factory);
|
||
app.render();
|
||
}
|
||
}
|
||
|
||
```
|
||
|
||
|
||
|
||
### 模板方法
|
||
|
||
**核心思想**:
|
||
|
||
在抽象父类中定义**算法骨架**(固定**执行顺序**),把某些可变步骤留给子类重写;调用方只用模板方法,保证流程一致。
|
||
|
||
如果仅仅是把重复的方法抽取成公共函数,不叫模板方法!模板方法要设计算法骨架!!!
|
||
|
||
```text
|
||
Client ───▶ AbstractClass
|
||
├─ templateMethod() ←—— 固定流程
|
||
│ step1()
|
||
│ step2() ←—— 抽象,可变
|
||
│ step3()
|
||
└─ hookMethod() ←—— 可选覆盖
|
||
▲
|
||
│ extends
|
||
┌──────────┴──────────┐
|
||
│ ConcreteClassA/B… │
|
||
|
||
```
|
||
|
||
**示例:**
|
||
|
||
```java
|
||
// 1. 抽象模板
|
||
public abstract class AbstractDialog {
|
||
|
||
// 模板方法:固定调用顺序,设为 final 防止子类改流程
|
||
public final void show() {
|
||
initLayout();
|
||
bindEvent();
|
||
beforeDisplay(); // 钩子,可选
|
||
display();
|
||
afterDisplay(); // 钩子,可选
|
||
}
|
||
|
||
// 具体公共步骤
|
||
private void initLayout() {
|
||
System.out.println("加载通用布局文件");
|
||
}
|
||
|
||
// 需要子类实现的抽象步骤
|
||
protected abstract void bindEvent();
|
||
|
||
// 钩子方法,默认空实现
|
||
protected void beforeDisplay() {}
|
||
protected void afterDisplay() {}
|
||
|
||
private void display() {
|
||
System.out.println("弹出对话框");
|
||
}
|
||
}
|
||
|
||
// 2. 子类:登录对话框
|
||
public class LoginDialog extends AbstractDialog {
|
||
@Override
|
||
protected void bindEvent() {
|
||
System.out.println("绑定登录按钮事件");
|
||
}
|
||
@Override
|
||
protected void afterDisplay() {
|
||
System.out.println("focus 到用户名输入框");
|
||
}
|
||
}
|
||
|
||
// 3. 调用
|
||
public class Demo {
|
||
public static void main(String[] args) {
|
||
AbstractDialog dialog = new LoginDialog();
|
||
dialog.show();
|
||
/* 输出:
|
||
加载通用布局文件
|
||
绑定登录按钮事件
|
||
弹出对话框
|
||
focus 到用户名输入框
|
||
*/
|
||
}
|
||
}
|
||
```
|
||
|
||
**要点**
|
||
|
||
- **复用公共流程**:`initLayout()`、`display()` 写一次即可。
|
||
- **限制流程顺序**:`show()` 定为 `final`,防止子类乱改步骤。
|
||
- **钩子方法**:子类可选择性覆盖(如 `beforeDisplay`)。
|
||
|
||
|
||
|
||
### 策略模式
|
||
|
||
**核心思想**:
|
||
|
||
将可以互换的算法或行为抽象为独立的策略类,运行时由**上下文类(Context)**选择合适的策略对象去执行。调用方(Client)只依赖统一的接口,不关心具体实现。
|
||
|
||
```text
|
||
┌───────────────┐
|
||
│ Client │
|
||
└─────▲─────────┘
|
||
│ has-a
|
||
┌─────┴─────────┐ implements
|
||
│ Context │────────────┐ ┌──────────────┐
|
||
│ (使用者) │ strategy └─▶│ Strategy A │
|
||
└───────────────┘ ├──────────────┤
|
||
│ Strategy B │
|
||
└──────────────┘
|
||
```
|
||
|
||
```java
|
||
// 策略接口
|
||
public interface PaymentStrategy {
|
||
void pay(int amount);
|
||
}
|
||
|
||
// 策略A:微信支付
|
||
@Service("wechat")
|
||
public class WechatPay implements PaymentStrategy {
|
||
public void pay(int amount) {
|
||
System.out.println("使用微信支付 " + amount + " 元");
|
||
}
|
||
}
|
||
|
||
// 策略B:支付宝支付
|
||
@Service("alipay")
|
||
public class Alipay implements PaymentStrategy {
|
||
public void pay(int amount) {
|
||
System.out.println("使用支付宝支付 " + amount + " 元");
|
||
}
|
||
}
|
||
|
||
// 上下文类
|
||
public class PaymentContext {
|
||
private PaymentStrategy strategy;
|
||
|
||
public PaymentContext(PaymentStrategy strategy) {
|
||
this.strategy = strategy;
|
||
}
|
||
|
||
public void execute(int amount) {
|
||
strategy.pay(amount);
|
||
}
|
||
}
|
||
|
||
// 调用方
|
||
public class Main {
|
||
public static void main(String[] args) {
|
||
PaymentContext ctx = new PaymentContext(new WechatPay());
|
||
ctx.execute(100);
|
||
|
||
ctx = new PaymentContext(new Alipay());
|
||
ctx.execute(200);
|
||
}
|
||
}
|
||
```
|
||
|
||
下面有更优雅的策略选择方式!
|
||
|
||
|
||
|
||
### Spring集合自动注入
|
||
|
||
在策略、工厂、插件等模式中,经常需要维护**“策略名 → 策略对象”**的映射。Spring 可以通过 `Map<String, 接口类型>` **一次性注入**所有实现类。
|
||
|
||
```java
|
||
@Resource
|
||
private Map<String, IDiscountCalculateService> discountCalculateServiceMap;
|
||
```
|
||
|
||
**字段类型**:`Map<String, IDiscountCalculateService>`
|
||
|
||
- key—— **Bean 的名字**
|
||
- 默认是类名首字母小写 (`mjCalculateService`)
|
||
- 或者你在实现类上显式写的 `@Service("MJ")`
|
||
- **value** —— 那个实现类对应的**实例**
|
||
- **Spring 机制**:
|
||
1. 启动时扫描所有实现 `IDiscountCalculateService` 的 Bean。
|
||
2. 把它们按 “BeanName → Bean 实例” 的映射注入到这张 `Map` 里。
|
||
3. 你一次性就拿到了“策略字典”。
|
||
|
||
**示例:**
|
||
|
||
```java
|
||
// 上下文类:自动注入所有策略 Bean
|
||
@Component
|
||
@RequiredArgsConstructor
|
||
public class PaymentContext {
|
||
|
||
// key 为 Bean 名(如 "wechat"、"alipay"),value 为策略实例
|
||
private final Map<String, PaymentStrategy> paymentStrategyMap;
|
||
|
||
public void pay(String strategyKey, int amount) {
|
||
PaymentStrategy strategy = paymentStrategyMap.get(strategyKey);
|
||
if (strategy == null) {
|
||
throw new IllegalArgumentException("无匹配支付方式: " + strategyKey);
|
||
}
|
||
strategy.pay(amount);
|
||
}
|
||
}
|
||
|
||
// 调用方示例
|
||
@Component
|
||
@RequiredArgsConstructor
|
||
public class PaymentService {
|
||
|
||
private final PaymentContext paymentContext;
|
||
|
||
public void process() {
|
||
paymentContext.pay("wechat", 100); // 输出:使用微信支付 100 元
|
||
paymentContext.pay("alipay", 200); // 输出:使用支付宝支付 200 元
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
|
||
### 模板方法+策略模式
|
||
|
||
本项目的价格试算同时用了策略模式 + 模板方法模式:
|
||
|
||
**策略模式(Strategy)**:
|
||
`IDiscountCalculateService` 是策略接口;`ZKCalculateService`、`ZJCalculateService` ...是**可替换的折扣策略**(@Service("ZK") / @Service("ZJ") 作为选择键)。外部可以根据活动配置里的类型码选哪个实现来算价——这就是“运行时可切换算法”。
|
||
|
||
**模板方法模式(Template Method)**:
|
||
`AbstractDiscountCalculateService#calculate(...)` 把**共同流程**固定下来(先进行人群校验 → 计算优惠后价格),并把“**真正的计算**”这一步**延迟到子类**通过 `doCalculate(...)` 实现。
|
||
|
||
|
||
|
||
### 责任链
|
||
|
||
应用场景:日志系统、审批流程、权限校验——任何需要将请求按阶段传递、并由某一环节决定是否继续或终止处理的地方,都非常适合责链模式。
|
||
|
||

|
||
|
||
|
||
|
||
典型的责任链模式要点:
|
||
|
||
- **解耦请求发送者和处理者**:调用者只持有链头,不关心中间环节。
|
||
- **动态组装**:通过 `appendNext` 可以灵活地增加、删除或重排链上的节点。
|
||
- **可扩展**:新增处理逻辑只需继承 `AbstractLogicLink` 并实现 `apply`,不用改动已有代码。
|
||
|
||
#### 单实例链
|
||
|
||
可以理解成“**单向、单链表式**的链条”:每个节点只知道自己的下一个节点(`next`),链头只有一个入口。
|
||
你可以在启动或运行时**动态组装**:`head.appendNext(a).appendNext(b).appendNext(c);`
|
||
|
||
**T / D / R 是啥?**
|
||
|
||
- `T`:请求的**静态入参**(本次请求的主要数据)。
|
||
- `D`:**动态上下文**(链路里各节点共享、可读写的状态容器,比如日志收集、校验中间结果)。
|
||
- `R`:最终**返回结果**类型。
|
||
|
||
1)接口定义:`ILogicChainArmory<T, D, R>` 提供**添加**节点方法和**获取**节点
|
||
|
||
```java
|
||
// 定义了“链条组装”的最小能力:能拿到下一个节点、也能把下一个节点接上去
|
||
public interface ILogicChainArmory<T, D, R> {
|
||
|
||
// 获取当前节点的“下一个”处理者
|
||
ILogicLink<T, D, R> next();
|
||
|
||
// 把新的处理者挂到当前节点后面,并返回它(方便链式 append)
|
||
ILogicLink<T, D, R> appendNext(ILogicLink<T, D, R> next);
|
||
}
|
||
```
|
||
|
||
2)`ILogicLink<T, D, R>` 继承自 `ILogicChainArmory<T, D, R>`,并额外声明了**核心方法** `apply`
|
||
|
||
```java
|
||
// 真正的“处理节点”接口:在具备链条组装能力的基础上,还要能“处理请求”
|
||
public interface ILogicLink<T, D, R> extends ILogicChainArmory<T, D, R> {
|
||
R apply(T requestParameter, D dynamicContext) throws Exception;
|
||
}
|
||
```
|
||
|
||
3)抽象基类:`AbstractLogicLink`,提供了**责任链节点的通用骨架**,(保存 `next`、实现 `appendNext`/`next()`、以及一个便捷的 `protected next(...)`,这样具体的节点类就不用重复这些代码,真正的业务处理逻辑仍然交由子类去实现 `apply(...)`。
|
||
|
||
```java
|
||
// 抽象基类:大多数节点都可以继承它,避免重复写“组装链”的样板代码
|
||
public abstract class AbstractLogicLink<T, D, R> implements ILogicLink<T, D, R> {
|
||
|
||
// 指向“下一个处理者”的引用
|
||
private ILogicLink<T, D, R> next;
|
||
|
||
@Override
|
||
public ILogicLink<T, D, R> next() {
|
||
return next;
|
||
}
|
||
|
||
@Override
|
||
public ILogicLink<T, D, R> appendNext(ILogicLink<T, D, R> next) {
|
||
this.next = next;
|
||
return next; // 返回 next 以便连续 append,类似 builder
|
||
}
|
||
|
||
/**
|
||
* 便捷方法:当前节点决定“交给下一个处理者”
|
||
*/
|
||
protected R next(T requestParameter, D dynamicContext) throws Exception {
|
||
// 直接把请求丢给下一个节点继续处理
|
||
// 注意:这里假设 next 一定存在;实际项目里建议判空以免 NPE(见下文改进建议)
|
||
return next.apply(requestParameter, dynamicContext);
|
||
}
|
||
}
|
||
```
|
||
|
||
子类只需要继承 `AbstractLogicLink` 并实现 `apply(...)`:
|
||
|
||
- **能处理就处理**(并可选择直接返回,终止链条)。
|
||
- **不处理或处理后仍需后续动作**,就 `return next(requestParameter, dynamicContext)` 继续传递。
|
||
|
||
4)实现子类
|
||
|
||
```java
|
||
@Component
|
||
public class AuthLink extends AbstractLogicLink<Request, Context, Response> {
|
||
@Override
|
||
public Response apply(Request req, Context ctx) throws Exception {
|
||
if (!ctx.isAuthenticated()) {
|
||
// 未认证:立刻终止;也可以在这里构造一个标准错误响应返回
|
||
throw new UnauthorizedException();
|
||
}
|
||
// 认证通过,继续下一个环节
|
||
return next(req, ctx);
|
||
}
|
||
}
|
||
@Component
|
||
public class LoggingLink extends AbstractLogicLink<Request, Context, Response> {
|
||
@Override
|
||
public Response apply(Request req, Context ctx) throws Exception {
|
||
System.out.println("Request received: " + req);
|
||
return next(req, ctx);
|
||
}
|
||
}
|
||
@Component
|
||
public class BusinessLogicLink extends AbstractLogicLink<Request, Context, Response> {
|
||
@Override
|
||
public Response apply(Request req, Context ctx) throws Exception {
|
||
// 业务逻辑...
|
||
return new Response(...);
|
||
}
|
||
}
|
||
```
|
||
|
||
5)组装链
|
||
|
||
```java
|
||
@Configuration
|
||
@RequiredArgsConstructor
|
||
public class LogicChainFactory {
|
||
|
||
private final AuthLink authLink;
|
||
private final LoggingLink loggingLink;
|
||
private final BusinessLogicLink businessLogicLink;
|
||
|
||
@Bean
|
||
public ILogicLink<Request, Context, Response> logicChain() {
|
||
return authLink
|
||
.appendNext(loggingLink)
|
||
.appendNext(businessLogicLink);
|
||
}
|
||
}
|
||
```
|
||
|
||
示例图:
|
||
|
||
```text
|
||
AuthLink.apply
|
||
└─▶ LoggingLink.apply
|
||
└─▶ BusinessLogicLink.apply
|
||
└─▶ 返回 Response
|
||
```
|
||
|
||
这种模式链上的每个节点都手动 `next()`到下一节点。
|
||
|
||
|
||
|
||
#### 多实例链1
|
||
|
||
以上是单例链,即只能创建一条链;比如A->B->C,不能创建别的链,因为节点Bean是单例的,如果创别的链会导致指针引用错误!!!
|
||
|
||
如果想变成多例链:
|
||
|
||
1)节点由默认的单例模式改为原型模式:
|
||
|
||
```java
|
||
@Component
|
||
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
|
||
public class A extends AbstractLogicLink<Req, Ctx, Resp> { ... }
|
||
```
|
||
|
||
2)组装链的时候注明不同链的bean名称:
|
||
|
||
```java
|
||
/** 全局唯一链:A -> B -> C */
|
||
@Bean("chainABC")
|
||
public ILogicLink<Req, Ctx, Resp> chainABC() {
|
||
A a = aProvider.getObject();
|
||
B b = bProvider.getObject();
|
||
C c = cProvider.getObject();
|
||
return a.appendNext(b).appendNext(c); // 返回链头 a
|
||
}
|
||
|
||
/** 全局唯一链:A -> C */
|
||
@Bean("chainAC")
|
||
public ILogicLink<Req, Ctx, Resp> chainAC() {
|
||
A a = aProvider.getObject();
|
||
C c = cProvider.getObject();
|
||
return a.appendNext(c); // 返回链头 a(另一套实例)
|
||
}
|
||
```
|
||
|
||
|
||
|
||
#### 多实例链2
|
||
|
||
```java
|
||
/**
|
||
* 通用逻辑处理器接口 —— 责任链中的「节点」要实现的核心契约。
|
||
*/
|
||
public interface ILogicHandler<T, D, R> {
|
||
|
||
/**
|
||
* 默认的 next占位实现,方便节点若不需要向后传递时直接返回 null。
|
||
*/
|
||
default R next(T requestParameter, D dynamicContext) {
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 节点的核心处理方法。
|
||
*/
|
||
R apply(T requestParameter, D dynamicContext) throws Exception;
|
||
|
||
}
|
||
|
||
```
|
||
|
||
```java
|
||
/**
|
||
* 业务链路容器 —— 双向链表实现,同时实现 ILogicHandler,从而可以被当作单个节点使用。
|
||
*/
|
||
public class BusinessLinkedList<T, D, R> extends LinkedList<ILogicHandler<T, D, R>> implements ILogicHandler<T, D, R>{
|
||
|
||
public BusinessLinkedList(String name) {
|
||
super(name);
|
||
}
|
||
|
||
/**
|
||
* BusinessLinkedList是头节点,它的apply方法就是循环调用后面的节点,直至返回。
|
||
* 遍历并执行链路。
|
||
*/
|
||
@Override
|
||
public R apply(T requestParameter, D dynamicContext) throws Exception {
|
||
Node<ILogicHandler<T, D, R>> current = this.first;
|
||
// 顺序执行,直到链尾或返回结果
|
||
while (current != null) {
|
||
ILogicHandler<T, D, R> handler = current.item;
|
||
R result = handler.apply(requestParameter, dynamicContext);
|
||
if (result != null) {
|
||
// 节点命中,立即返回
|
||
return result;
|
||
}
|
||
//result==null,则交给那一节点继续处理
|
||
current = current.next;
|
||
}
|
||
// 全链未命中
|
||
return null;
|
||
}
|
||
}
|
||
```
|
||
|
||
```java
|
||
/**
|
||
* 链路装配工厂 —— 负责把一组 ILogicHandler 顺序注册到 BusinessLinkedList 中。
|
||
*/
|
||
public class LinkArmory<T, D, R> {
|
||
|
||
private final BusinessLinkedList<T, D, R> logicLink;
|
||
|
||
/**
|
||
* @param linkName 链路名称,便于日志排查
|
||
* @param logicHandlers 节点列表,按传入顺序链接
|
||
*/
|
||
@SafeVarargs
|
||
public LinkArmory(String linkName, ILogicHandler<T, D, R>... logicHandlers) {
|
||
logicLink = new BusinessLinkedList<>(linkName);
|
||
for (ILogicHandler<T, D, R> logicHandler: logicHandlers){
|
||
logicLink.add(logicHandler);
|
||
}
|
||
}
|
||
|
||
/** 返回组装完成的链路 */
|
||
public BusinessLinkedList<T, D, R> getLogicLink() {
|
||
return logicLink;
|
||
}
|
||
|
||
}
|
||
|
||
//工厂类,可以定义多条责任链,每条有自己的Bean名称区分。
|
||
@Bean("tradeRuleFilter")
|
||
public BusinessLinkedList<TradeRuleCommandEntity, DynamicContext, TradeRuleFilterBackEntity> tradeRuleFilter(ActivityUsabilityRuleFilter activityUsabilityRuleFilter, UserTakeLimitRuleFilter userTakeLimitRuleFilter) {
|
||
// 1. 组装链
|
||
LinkArmory<TradeRuleCommandEntity, DynamicContext, TradeRuleFilterBackEntity> linkArmory =
|
||
new LinkArmory<>("交易规则过滤链", activityUsabilityRuleFilter, userTakeLimitRuleFilter);
|
||
|
||
// 2. 返回链容器(即可作为责任链使用)
|
||
return linkArmory.getLogicLink();
|
||
}
|
||
```
|
||
|
||
示例图:
|
||
|
||
```text
|
||
BusinessLinkedList.apply ←─ 只有这一层在栈里
|
||
while 循环:
|
||
├─▶ 调用 ActivityUsability.apply → 返回 null → 继续
|
||
├─▶ 调用 UserTakeLimit.apply → 返回 null → 继续
|
||
└─▶ 调用 ... → 返回 Result → break
|
||
|
||
```
|
||
|
||
链头拿着“游标”一个个跑,节点只告诉“命中 / 未命中”。
|
||
|
||
这里无需把节点改为原型模式,也可以实现多例链,因为由双向链表`BusinessLinkedList` 负责**保存链路关系和推进执行**,而`ILogicHandler`节点本身**不再保存 `next` 指针**,所以它们之间没有共享可变状态。
|
||
|
||
|
||
|
||
本项目中使用多实例链2,有以下场景:
|
||
|
||
**一、拼团「锁单前」校验链**
|
||
|
||
目标:在真正锁单前把“活动有效性 / 用户参与资格 / 可用库存”一口气校清楚,避免后续回滚。
|
||
|
||
1.活动有效性校验 `ActivityUsability` (当前时间是否早于活动截止时间)
|
||
|
||
2.用户可参与活动次数校验 `UserTakeLimitRuleFilter`(默认用户只可参与一次拼团)
|
||
|
||
3.剩余库存校验 `TeamStockOccupyRuleFilter`(可能同时有多人点击参与当前拼团,尝试抢占库存,仅部分人可通过校验。)
|
||
|
||
校验通过方可进行真正的锁单。
|
||
|
||
|
||
|
||
**二、交易结算校验链**
|
||
|
||
1.渠道黑名单校验 `SCRuleFilter`:某签约渠道下架/风控拦截,禁止结算。
|
||
|
||
2.外部单号校验 `OutTradeNoRuleFilter`:查营销订单;不存在或已退单(`CLOSE`)→ 不结算。
|
||
|
||
3.可结算时间校验 `SettableRuleFilter`:结算时间必须在拼团有效期内(`outTradeTime < team.validEndTime`),比如发起
|
||
|
||
拼团一个小时之内要结算完毕。
|
||
|
||
4.结束节点`EndRuleFilter`:整理上下文到返回对象,作为结算规则校验的产出。
|
||
|
||
检验通过方可进入真正的结算。
|
||
|
||
|
||
|
||
**三、交易退单执行链**
|
||
|
||
1.数据加载 `DataNodeFilter`:按 `userId + outTradeNo` 查询营销订单与拼团信息,写入上下文。
|
||
|
||
2.重复退单检查 `UniqueRefundNodeFilter`:订单已是 `CLOSE` → 视为幂等重复,直接返回。
|
||
|
||
3.退单策略执行 `RefundOrderNodeFilter`:依据“拼团态 + 订单态”选用具体退单策略 `IRefundOrderStrategy`,执行退款/解锁/改库并返回成功结果。
|
||
|
||
本身就是完整的退单流程。
|
||
|
||
|
||
|
||
### 规则树流程
|
||
|
||

|
||
|
||
**整体分层思路**
|
||
|
||
| 分层 | 作用 | 关键对象 |
|
||
| -------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||
| **通用模板层** | 抽象出与具体业务无关的「规则树」骨架,解决 *如何找到并执行策略* 的共性问题 | `StrategyMapper`、`StrategyHandler`、`AbstractStrategyRouter<T,D,R>` |
|
||
| **业务装配层** | 基于模板,自由拼装出 *一棵* 贴合业务流程的策略树 | `RootNode / SwitchNode / MarketNode / EndNode …` |
|
||
| **对外暴露层** | 通过 **工厂 + 服务支持类** 将整棵树封装成一个可直接调用的 `StrategyHandler`,并交给 Spring 整体托管 | `DefaultActivityStrategyFactory`、`AbstractGroupBuyMarketSupport` |
|
||
|
||
**通用模板层:规则树的“骨架”**
|
||
|
||
| 角色 | 职责 | 关系 |
|
||
| ------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||
| `StrategyMapper` | **映射器**:依据 `requestParameter + dynamicContext` 选出 *下一个* 策略节点 | 被 `AbstractStrategyRouter` 调用 |
|
||
| `StrategyHandler` | **处理器**:真正执行业务逻辑;`apply` 结束后可返回结果或继续路由 | 节点本身 / 路由器本身都是它的实现 |
|
||
| `AbstractStrategyRouter<T,D,R>` | **路由模板**:① 调用 `get(...)` 找到合适的 `StrategyHandler`;② 调用该 handler 的 `apply(...)`;③ 若未命中则走 `defaultStrategyHandler` | 同时实现 `StrategyMapper` 与 `StrategyHandler`,但自身保持 *抽象*,把细节延迟到子类 |
|
||
|
||
**业务装配层:一棵可编排的策略树**
|
||
|
||
```text
|
||
RootNode -> SwitchNode -> MarketNode -> EndNode
|
||
↘︎ OtherNode ...
|
||
```
|
||
|
||
- 每个节点
|
||
|
||
继承 `AbstractGroupBuyMarketSupport`(业务基类)
|
||
|
||
- 实现 `get(...)`:决定当前节点的下一跳是哪一个节点
|
||
- 实现 `apply(...)`:实现节点自身应做的业务动作(或继续下钻)
|
||
|
||
- 组合方式
|
||
|
||
- **路由是“数据驱动”的**:并非工厂把链写死,而是**节点在运行期**根据 `request + context` 决定下一跳(可能是ERROR_NODE或END_NODE),灵活插拔。
|
||
|
||
|
||
|
||
**对外暴露层:工厂 + 服务支持类**
|
||
|
||
| 组件 | 主要职责 |
|
||
| --------------------------------------------- | ------------------------------------------------------------ |
|
||
| `DefaultActivityStrategyFactory` (`@Service`) | 仅负责把 `RootNode` 暴露为 `StrategyHandler` 入口(交由 Spring 管理,方便注入)。 |
|
||
| `AbstractGroupBuyMarketSupport` | **业务服务基类**:封装拼团场景下**共用**的查询、工具方法;供每个**节点**继承使用 |
|
||
|
||
|
||
|
||
#### 本项目执行总览:
|
||
|
||
**调用入口**:`factory.strategyHandler()` → 返回 `RootNode`(实现了 `StrategyHandler`)。
|
||
|
||
**执行流程**:
|
||
|
||
`apply(...)`:模板入口,**先**跑 `multiThread(...)` 预取/并发任务,**再**跑 `doApply(...)`。
|
||
|
||
`doApply(...)`:每个节点自己的业务;**通常在末尾调用** `router(...)` 继续下一个节点(你现在就是这样写的:`return router(request, ctx);`)。也可以在某些节点“短路返回”,不再路由。
|
||
|
||
`router(...)`:内部调用当前节点的 `get(...)` 来**挑选下一节点**`next`,若存在就调用 `next.apply(...)` 递归推进;若不存在(或是到达 `EndNode`),则**收束返回**。
|
||
|
||
|
||
|
||
**RootNode**
|
||
|
||
- 校验必填:`userId/goodsId/source/channel`。
|
||
- 合法则路由到 `SwitchNode`;非法直接抛 `ILLEGAL_PARAMETER`。
|
||
|
||
**SwitchNode(总开关、不区分活动,做总体的降级限流)**
|
||
|
||
- 调用 `repository.downgradeSwitch()` 判断是否降级;是则抛 `E0003`。
|
||
- 调用 `repository.cutRange(userId)` 做切量;不在范围抛 `E0004`。
|
||
- 通过后路由到 `MarketNode`。
|
||
|
||
**MarketNode**
|
||
|
||
- **multiThread(...)** 中并发拉取:
|
||
- 拼团活动配置 `GroupBuyActivityDiscountVO`
|
||
- 商品信息 `SkuVO`
|
||
- 写入 `DynamicContext`
|
||
- **doApply(...)**
|
||
- 读取配置 + SKU,按 `marketPlan` 选 `IDiscountCalculateService`,计算 `payPrice` / `deductionPrice` 并写回上下文。
|
||
- 路由判定:
|
||
- 若配置/商品/`deductionPrice` 有缺失 → `ErrorNode`
|
||
- 否则 → `TagNode`
|
||
|
||
**TagNode(业务相关,部分人不在本次活动范围内!)**
|
||
|
||
- 若活动没配置 `tagId` → 视为不限定人群:`visible=true`、`enable=true`。
|
||
- 否则通过 `repository.isTagCrowdRange(tagId, userId)` 判断是否在人群内,并据此更新 `visible/enable`。
|
||
- 路由到 `EndNode`。
|
||
|
||
**EndNode**
|
||
|
||
- 从 `DynamicContext` 读取:`skuVO / payPrice / deductionPrice / groupBuyActivityDiscountVO / visible / enable`;
|
||
- **构建并返回**最终的 `TrialBalanceEntity`,链路终止。
|
||
|
||
**ErrorNode**
|
||
|
||
- 统一异常出口;若无配置/无商品,抛 `E0002`;否则可返回空结果作为兜底;
|
||
- 返回后走 `defaultStrategyHandler`(结束)。
|
||
|
||
|
||
|