## 设计模式 ### 单例模式 #### 懒汉 ```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` **一次性注入**所有实现类。 ```java @Resource private Map discountCalculateServiceMap; ``` **字段类型**:`Map` - 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 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(...)` 实现。 ### 责任链 应用场景:日志系统、审批流程、权限校验——任何需要将请求按阶段传递、并由某一环节决定是否继续或终止处理的地方,都非常适合责链模式。 ![image-20250808215823342](https://pic.bitday.top/i/2025/08/08/zojzfm-0.png) 典型的责任链模式要点: - **解耦请求发送者和处理者**:调用者只持有链头,不关心中间环节。 - **动态组装**:通过 `appendNext` 可以灵活地增加、删除或重排链上的节点。 - **可扩展**:新增处理逻辑只需继承 `AbstractLogicLink` 并实现 `apply`,不用改动已有代码。 #### 单实例链 可以理解成“**单向、单链表式**的链条”:每个节点只知道自己的下一个节点(`next`),链头只有一个入口。 你可以在启动或运行时**动态组装**:`head.appendNext(a).appendNext(b).appendNext(c);` **T / D / R 是啥?** - `T`:请求的**静态入参**(本次请求的主要数据)。 - `D`:**动态上下文**(链路里各节点共享、可读写的状态容器,比如日志收集、校验中间结果)。 - `R`:最终**返回结果**类型。 1)接口定义:`ILogicChainArmory` 提供**添加**节点方法和**获取**节点 ```java // 定义了“链条组装”的最小能力:能拿到下一个节点、也能把下一个节点接上去 public interface ILogicChainArmory { // 获取当前节点的“下一个”处理者 ILogicLink next(); // 把新的处理者挂到当前节点后面,并返回它(方便链式 append) ILogicLink appendNext(ILogicLink next); } ``` 2)`ILogicLink` 继承自 `ILogicChainArmory`,并额外声明了**核心方法** `apply` ```java // 真正的“处理节点”接口:在具备链条组装能力的基础上,还要能“处理请求” public interface ILogicLink extends ILogicChainArmory { R apply(T requestParameter, D dynamicContext) throws Exception; } ``` 3)抽象基类:`AbstractLogicLink`,提供了**责任链节点的通用骨架**,(保存 `next`、实现 `appendNext`/`next()`、以及一个便捷的 `protected next(...)`,这样具体的节点类就不用重复这些代码,真正的业务处理逻辑仍然交由子类去实现 `apply(...)`。 ```java // 抽象基类:大多数节点都可以继承它,避免重复写“组装链”的样板代码 public abstract class AbstractLogicLink implements ILogicLink { // 指向“下一个处理者”的引用 private ILogicLink next; @Override public ILogicLink next() { return next; } @Override public ILogicLink appendNext(ILogicLink 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 { @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 { @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 { @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 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 { ... } ``` 2)组装链的时候注明不同链的bean名称: ```java /** 全局唯一链:A -> B -> C */ @Bean("chainABC") public ILogicLink 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 chainAC() { A a = aProvider.getObject(); C c = cProvider.getObject(); return a.appendNext(c); // 返回链头 a(另一套实例) } ``` #### 多实例链2 ```java /** * 通用逻辑处理器接口 —— 责任链中的「节点」要实现的核心契约。 */ public interface ILogicHandler { /** * 默认的 next占位实现,方便节点若不需要向后传递时直接返回 null。 */ default R next(T requestParameter, D dynamicContext) { return null; } /** * 节点的核心处理方法。 */ R apply(T requestParameter, D dynamicContext) throws Exception; } ``` ```java /** * 业务链路容器 —— 双向链表实现,同时实现 ILogicHandler,从而可以被当作单个节点使用。 */ public class BusinessLinkedList extends LinkedList> implements ILogicHandler{ public BusinessLinkedList(String name) { super(name); } /** * BusinessLinkedList是头节点,它的apply方法就是循环调用后面的节点,直至返回。 * 遍历并执行链路。 */ @Override public R apply(T requestParameter, D dynamicContext) throws Exception { Node> current = this.first; // 顺序执行,直到链尾或返回结果 while (current != null) { ILogicHandler 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 { private final BusinessLinkedList logicLink; /** * @param linkName 链路名称,便于日志排查 * @param logicHandlers 节点列表,按传入顺序链接 */ @SafeVarargs public LinkArmory(String linkName, ILogicHandler... logicHandlers) { logicLink = new BusinessLinkedList<>(linkName); for (ILogicHandler logicHandler: logicHandlers){ logicLink.add(logicHandler); } } /** 返回组装完成的链路 */ public BusinessLinkedList getLogicLink() { return logicLink; } } //工厂类,可以定义多条责任链,每条有自己的Bean名称区分。 @Bean("tradeRuleFilter") public BusinessLinkedList tradeRuleFilter(ActivityUsabilityRuleFilter activityUsabilityRuleFilter, UserTakeLimitRuleFilter userTakeLimitRuleFilter) { // 1. 组装链 LinkArmory 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`,执行退款/解锁/改库并返回成功结果。 本身就是完整的退单流程。 ### 规则树流程 ![image-20250725120957709](https://pic.bitday.top/i/2025/07/25/k01knr-0.png) **整体分层思路** | 分层 | 作用 | 关键对象 | | -------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | **通用模板层** | 抽象出与具体业务无关的「规则树」骨架,解决 *如何找到并执行策略* 的共性问题 | `StrategyMapper`、`StrategyHandler`、`AbstractStrategyRouter` | | **业务装配层** | 基于模板,自由拼装出 *一棵* 贴合业务流程的策略树 | `RootNode / SwitchNode / MarketNode / EndNode …` | | **对外暴露层** | 通过 **工厂 + 服务支持类** 将整棵树封装成一个可直接调用的 `StrategyHandler`,并交给 Spring 整体托管 | `DefaultActivityStrategyFactory`、`AbstractGroupBuyMarketSupport` | **通用模板层:规则树的“骨架”** | 角色 | 职责 | 关系 | | ------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | `StrategyMapper` | **映射器**:依据 `requestParameter + dynamicContext` 选出 *下一个* 策略节点 | 被 `AbstractStrategyRouter` 调用 | | `StrategyHandler` | **处理器**:真正执行业务逻辑;`apply` 结束后可返回结果或继续路由 | 节点本身 / 路由器本身都是它的实现 | | `AbstractStrategyRouter` | **路由模板**:① 调用 `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`(结束)。