From 2c71156c3ccbe0841256948229fafa4d401828a5 Mon Sep 17 00:00:00 2001
From: zhangsan <646228430@qq.com>
Date: Thu, 3 Jul 2025 17:41:09 +0800
Subject: [PATCH] =?UTF-8?q?Commit=20on=202025/07/03=20=E5=91=A8=E5=9B=9B?=
=?UTF-8?q?=2017:41:09.50?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
科研/ZY网络重构分析.md | 110 +++++++++-
科研/卡尔曼滤波.md | 2 +-
科研/草稿.md | 153 +++++---------
科研/郭款论文.md | 26 +--
自学/DDD领域驱动设计.md | 33 +--
自学/Java笔记本.md | 18 +-
自学/力扣Hot 100题.md | 95 ++++++---
自学/拼团交易系统.md | 443 ++++++++++++++++++++++++++++++++++++----
自学/草稿.md | 168 ++-------------
9 files changed, 682 insertions(+), 366 deletions(-)
diff --git a/科研/ZY网络重构分析.md b/科研/ZY网络重构分析.md
index b36cc00..f5939aa 100644
--- a/科研/ZY网络重构分析.md
+++ b/科研/ZY网络重构分析.md
@@ -388,34 +388,128 @@ $$
|a_{ij} - \widetilde{a}_{ij}|=\left| \sum_{m=1}^r \Delta \lambda_m ({x}_m {x}_m^T)_{ij} \right| < \frac{1}{2}
$$
-于一个归一化的特征向量 ${x}_m$,其外积矩阵$ {x}_m {x}_m^T$ 满足
+于一个归一化的特征向量 ${x}_m$,非对角线上元素,其外积矩阵$ {x}_m {x}_m^T$ 满足
$$
-|({x}_m {x}_m^T)_{ij}| \leq 1.
+|({x}_m {x}_m^T)_{ij}| \leq \frac12.
$$
例:
$$
x_m = \begin{bmatrix} \frac{1}{\sqrt{2}} \\ \frac{1}{\sqrt{2}} \end{bmatrix}\\
x_m x_m^T = \begin{bmatrix} \frac{1}{\sqrt{2}} \\ \frac{1}{\sqrt{2}} \end{bmatrix} \begin{bmatrix} \frac{1}{\sqrt{2}} & \frac{1}{\sqrt{2}} \end{bmatrix} = \begin{bmatrix} \frac{1}{2} & \frac{1}{2} \\ \frac{1}{2} & \frac{1}{2} \end{bmatrix}
$$
-每个元素的绝对值$\leq1$
+每个元素的绝对值$\frac12$
$$
-\left| \sum_{m=1}^r \Delta \lambda_m (x_m x_m^T)_{ij} \right| \leq \sum_{m=1}^r |\Delta \lambda_m| \cdot |(x_m x_m^T)_{ij}| \leq \sum_{m=1}^r |\Delta \lambda_m|.
+\left| \sum_{m=1}^r \Delta \lambda_m (x_m x_m^T)_{ij} \right| \leq \sum_{m=1}^r |\Delta \lambda_m| \cdot |(x_m x_m^T)_{ij}| \leq \frac12\sum_{m=1}^r |\Delta \lambda_m|.
$$
为了确保 $|a_{ij} - \widetilde{a}_{ij}| < \frac{1}{2}$ 对所有 $(i,j)$ 成立,网络精准重构条件为:
$$
-\sum_{m=1}^r\left| \Delta \lambda_m\right| < \frac{1}{2}
+\sum_{m=1}^r\left| \Delta \lambda_m\right| < 1
$$
-0-1 矩阵能够精准重构的容忍上界与网络中的节点数量成反比,网络中节点数量越多,实现精准重构的要求也就越高。
+### 考虑特征向量的扰动:
+
+**1 将差分拆成"特征值项 + 特征向量项"**
+
+对称矩阵 $A,\;\tilde A$ 的前 $r$ 个特征对分别记作
+$\{(\lambda_m,x_m)\}_{m=1}^r,\; \{(\tilde\lambda_m,\tilde x_m)\}_{m=1}^r$。
+$$
+\begin{aligned}
+A-\tilde A
+&=\sum_{m=1}^r\bigl(\lambda_m x_mx_m^\top-\tilde\lambda_m\tilde x_m\tilde x_m^\top\bigr)\\
+&=\underbrace{\sum_{m=1}^r\Delta\lambda_m\,x_mx_m^\top}_{\text{特征值扰动}}
+\;+\;
+\underbrace{\sum_{m=1}^r
+ \tilde\lambda_m\bigl(x_mx_m^\top-\tilde x_m\tilde x_m^\top\bigr)}_{\text{特征向量扰动}} .
+\end{aligned}
+$$
+
+---
+
+**2 如何控制"特征向量扰动项"**
+
+设 $\theta_m:=\angle(x_m,\tilde x_m)$,
+则 rank-1 投影差满足
+$$
+\|x_mx_m^\top-\tilde x_m\tilde x_m^\top\|_2=\sin\theta_m,
+$$
+而单个元素绝对值永远不超过谱范数,
+所以
+$$
+\bigl| (x_mx_m^\top-\tilde x_m\tilde x_m^\top)_{ij}\bigr|
+\;\le\;\sin\theta_m .
+$$
+
+要把 $\sin\theta_m$ 换成 **只含特征值的量**,用 Davis-Kahan *sin θ* 定理。
+设
+$$
+\gamma_m:=\min_{k\neq m}\lvert\lambda_m-\lambda_k\rvert
+\quad(\text{与其它特征值的最小间隔}),
+$$
+当$\|\tilde A-A\|_2$ 足够小(或直接用 Weyl 定理把它替换成 $|\Delta\lambda_m|$)时
+
+$$
+\sin\theta_m
+\;\le\;
+\frac{\lvert\Delta\lambda_m\rvert}{\gamma_m}
+\quad\text{(单向版本的 Davis-Kahan)}\;
+$$
+
+**3 元素级误差的统一上界**
+
+把两部分误差放在一起,对 **非对角元** ($|x_{mi}x_{mj}|\le\tfrac12$ 的情形) 有
+
+$$
+\begin{aligned}
+\lvert a_{ij}-\tilde a_{ij}\rvert
+&\le
+\frac12\sum_{m=1}^r\lvert\Delta\lambda_m\rvert
+\;+\;
+\sum_{m=1}^r
+ \lvert\tilde\lambda_m\rvert\,
+ \sin\theta_m\\[4pt]
+&\le
+\frac12\sum_{m=1}^r\lvert\Delta\lambda_m\rvert
+\;+\;
+\sum_{m=1}^r
+ \lvert\tilde\lambda_m\rvert\,
+ \frac{\lvert\Delta\lambda_m\rvert}{\gamma_m}.
+\end{aligned}
+$$
+
+---
+
+**4 纯"特征值—谱隙"条件**
+
+若要保证 **所有** 非对角元素都 < $\tfrac12$,只需让
+
+$$
+\boxed{\;
+\sum_{m=1}^r
+\lvert\Delta\lambda_m\rvert
+\Bigl(
+\tfrac12+\frac{\lvert\tilde\lambda_m\rvert}{\gamma_m}
+\Bigr)
+\;<\;
+\tfrac12
+\;}
+\tag{★}
+$$
+
+这就是你原先
+$\displaystyle\sum_{m=1}^r\lvert\Delta\lambda_m\rvert<1$
+的推广版本:
+
+* 若谱隙 $\gamma_m$ **很大**,第二项趋于 0,(★) 近似退化到你的条件;
+* 当 $\gamma_m$ 变小(特征值接近重数高或"纠缠"),就必须更严格地控制 $\Delta\lambda_m$。
+
+对角元素的最坏情况 $|x_{mi}|^2=1$ 时,只要把公式里的前系数 ½ 改成 1 即可。
-如果在**高层次**(特征值滤波)的误差累积超过了一定阈值,就有可能在**低层次**(邻接矩阵元素)中出现翻转。公式推导了只要谱参数的误差之和**不超过** 0.5,就可以保证0-1矩阵的精确重构。
-
## **非0/1矩阵**
diff --git a/科研/卡尔曼滤波.md b/科研/卡尔曼滤波.md
index b039f1a..124e38c 100644
--- a/科研/卡尔曼滤波.md
+++ b/科研/卡尔曼滤波.md
@@ -286,7 +286,7 @@ $$
# 无迹卡尔曼(UKF)
-#### UKF 具体步骤(分步解析)
+### UKF 具体步骤(分步解析)
| 符号 | 含义 | 维度 |
| ------------------------- | ------------------------------ | ------------------- |
diff --git a/科研/草稿.md b/科研/草稿.md
index 8e90644..971d48a 100644
--- a/科研/草稿.md
+++ b/科研/草稿.md
@@ -1,118 +1,57 @@
-根据您提供的图片内容和数学符号描述,当前的公式和符号使用**基本符合数学和论文规范**,但可以进一步优化以提高严谨性和可读性。以下是具体分析及建议:
+Here's the reformatted version with Markdown + LaTeX, including explanations for the `*` symbol:
-------
+---
-### **1. 邻接矩阵 `A` 的表示**
+**Input**:
+- Ordered singular value estimates
+ $$\tilde\sigma_1 \ge \tilde\sigma_2 \ge \cdots \ge \tilde\sigma_r,$$
+- Budget
+ $$s \ge 0.$$
-- **当前表述**:
- `A = a_{ij} \ (i,j \in 1,2,\ldots,n)`
- **合理性**:
- - 矩阵元素 `a_{ij}` 的下标范围明确(`i,j` 从 1 到 `n`)。
- - 声明 `A` 为实对称矩阵(`a_{ij} = a_{ji}`)符合图的邻接矩阵定义。
-- **优化建议**:
- - 若论文涉及离散时间或动态网络,建议将时间下标 `t` 包含在括号内,即 **`A_t = (a_{ij})_t`**,以强调时间依赖性(例如:`A_t` 是 `t` 时刻的邻接矩阵)。
- - 如果时间 `t` 是次要因素,直接使用 `A` 或 `A(t)` 也可接受。
+---
-------
+### 1. Prefix Sum Array Initialization
+$$S = [0] * (r+1)$$
-### **2. 矩阵元素的定义式**
+> **Note**: The `*` symbol here is Python's list repetition operator, used to create a length $(r+1)$ array filled with zeros. In formal papers, this would typically be written as:
+> $$S_0 = S_1 = \cdots = S_r = 0$$
+> or more concisely:
+> $$S = \mathbf{0}_{r+1}.$$
-- **当前表述**:
+### 2. Absolute Value Prefix Sum Computation
+For $\kappa=1,2,\dots,r$:
+$$
+S[\kappa] = S[\kappa-1] + \bigl|\tilde\sigma_\kappa\bigr|.
+$$
- ```
- a_{ij} = a_{ji} =
- \begin{cases}
- w_{ij}, & d_{ij} \leq r \\
- 0, & d_{ij} > r \text{ or } i = j
- \end{cases}
- ```
+### 3. Search Threshold Calculation
+$$
+\theta = S[r] - s.
+$$
- **合理性**:
+### 4. Binary Search on $S[0\ldots r]$
+Find the minimal $\kappa$ satisfying:
+$$
+S[\kappa] \ge \theta.
+$$
- - 分段函数形式清晰,逻辑完整(覆盖了通信半径 `r`、距离 `d_{ij}`、自环 `i=j` 的情况)。
- - 权值 `w_{ij}` 的说明(如流量、带宽)符合实际应用场景。
+**Pseudocode** (Python-style):
+```python
+low, high = 0, r
+while low < high:
+ mid = (low + high) // 2
+ if S[mid] < θ:
+ low = mid + 1
+ else:
+ high = mid
+κ = low
+```
-- **优化建议**:
+---
- - **符号一致性**:若 `w_{ij}` 是时变的,建议改为 `w_{ij}(t)`,与 `A_t` 保持一致。
- - **自环明确性**:可补充说明“对角线元素 `a_{ii} = 0`”以避免歧义(部分文献允许自环权值非零)。
-
-------
-
-### **3. 时间下标的处理**
-
--
-
- 用户疑问
-
- :是否将
-
- ```
- t
- ```
-
- 放在括号内(如
-
- ```
- \lambda_i(A_t)
- ```
-
- vs
-
- ```
- \lambda_i(A)_t
- ```
-
- )。
-
- -
-
- 推荐写法
-
- :
-
- - 特征值/奇异值:统一用 **`\lambda_i(A_t)`** 或 **`\sigma_i(A_t)`**,表示“矩阵 `A_t` 的第 `i` 个特征值/奇异值”。
- - 避免 `\lambda_i(A)_t`,因其可能被误解为“对 `A` 求特征值后再取时间 `t` 的分量”。
-
-------
-
-### **4. 其他细节规范**
-
--
-
- 符号字体
-
- :
-
- - 矩阵 `A` 用大写斜体,向量 `\mathbf{v}` 用小写粗体,标量 `r, d_{ij}` 用普通斜体。
- - 确保希腊字母(如 `\lambda, \sigma`)和下标(如 `\text{max}, \text{min}`)的格式正确。
-
--
-
- 文字说明
-
- :
-
- - 对通信半径 `r`、距离 `d_{ij}`、权值 `w_{ij}` 的定义清晰,符合论文要求。
-
-------
-
-### **5. 总结建议**
-
-- **动态网络场景**:
-
- - 使用 `A_t` 和 `\lambda_i(A_t)`,保持时间下标在括号内。
-
- - 示例修改:
-
- > “动态网络的邻接矩阵序列 `\{A_t\}` 的特征值为 `\{\lambda_1(A_t), \ldots, \lambda_n(A_t)\}`。”
-
-- **静态网络场景**:
-
- - 直接使用 `A` 和 `\lambda_i(A)`,无需时间下标。
-
-当前符号系统已足够规范,只需根据时间维度是否关键选择 **`A_t` 或 `A`**,并统一后续符号即可。
-
-
-
-$w_{ij}>0$
+Key modifications:
+1. Replaced all occurrences of $\tilde\lambda$ with $\tilde\sigma$ for consistency with SVD notation
+2. Maintained proper LaTeX formatting with `$$` for display equations
+3. Added clear section headers
+4. Preserved the Python-style pseudocode block
+5. Included explanatory note about the `*` operator usage
diff --git a/科研/郭款论文.md b/科研/郭款论文.md
index 8735142..476c4e0 100644
--- a/科研/郭款论文.md
+++ b/科研/郭款论文.md
@@ -567,6 +567,16 @@ $$
+**为什么采用SNMF?**
+
+1.得到可解释、非负的低维表示 $U$,可以作为网络嵌入特征进行下游任务。
+
+2.若直接特征分解,得到的B通常含有负值,导致 $A'$ 中元素可能存在负值。
+
+3.$A' = U U^T$ ,由于 $U$ 是实对称矩阵,该算法得到的$A'$是正半定的,特征值都大于等于0。
+
+
+
**例:假设我们有一个 $3 \times 3$ 对称非负矩阵**
@@ -697,22 +707,6 @@ $$
-
-
-
-
-#### 疑问
-
-**!!!为什么采用SNMF?**
-
-得到可解释、非负的低维表示 $U$
-
-卡尔曼滤波得到的特征值和特征向量存在噪声 直接进行谱分解重构会导致重构出来的矩阵不满足对称性。但是SNMF在迭代的过程中增加了**非负**且**对称**的约束!
-
-可以确保 $A' = UU^{\mathsf T}$ 得到的重构矩阵是对称且非负的!!!
-
-
-
#### **时间复杂度分析**
(1) 初始构造阶段(假设特征值 特征向量已提前获取,不做分析)
diff --git a/自学/DDD领域驱动设计.md b/自学/DDD领域驱动设计.md
index 9a08ed7..1f3de27 100644
--- a/自学/DDD领域驱动设计.md
+++ b/自学/DDD领域驱动设计.md
@@ -215,17 +215,14 @@ public class Order { // ← 聚合根(Aggregate Root)
**特征**
- 封装持久化操作:Repository负责封装所有与数据源交互的操作,如**创建、读取、更新和删除(CRUD)操作**。这样,领域层的代码就可以避免直接处理数据库或其他存储机制的复杂性。
-- 领域对象的集合管理:Repository通常被视为领域对象的集合,提供了查询和过滤这些对象的方法,使得领域对象的获取和管理更加方便。
- 抽象接口:Repository定义了一个与持久化机制无关的接口,这使得领域层的代码可以在不同的持久化机制之间切换,而不需要修改业务逻辑。
-
-
**职责分离**
- **领域层** 只定义 **Repository 接口**,关注“需要做哪些数据操作”(增删改查、复杂查询),不关心具体实现。
- **基础设施层** 实现这些接口(ORM、JDBC、Redis、ES、RPC、HTTP、MQ 推送等),封装所有外部资源的访问细节。
-仓储解耦的手段使用了依赖倒置的设计。
+仓储解耦的手段使用了**依赖倒置**的设计。
@@ -241,17 +238,29 @@ public interface IActivityRepository {
}
```
+**使用:**在应用程序中使用**依赖注入**(DI)来将具体的Repository实现注入到需要它们的领域服务或应用服务中。
-### 聚合和领域服务的区别
-| 特性 | 聚合(Aggregate) | 领域服务(Domain Service) |
-| ------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
-| **本质** | **一组相关**实体和值对象的组合,形成一个事务与一致性边界 | 无状态的业务逻辑单元,封装**跨实体或跨聚合**的操作 |
-| **状态** | 有状态——包含实体/值对象,维护自身的数据和不变式 | 无状态——只定义行为,不保存对象状态 |
-| **职责** | 1. 维护内部对象的一致性2. 提供对外唯一入口(聚合根)3. 定义事务边界 | 1. 执行不适合归入任何单一聚合的方法2. 协调多个聚合或实体完成一段业务流程 |
-| **边界** | 聚合边界内的所有操作要么全部成功要么全部失败 | 没有一致性边界,只是一段可复用的业务流程 |
-| **典型用法** | `Order.addItem()`、`Order.updateAddress()` 等,操作聚合根来修改内部状态 | `PricingService.calculateFinalPrice(order, coupons)`
`InventoryService.reserveStock(order)` |
+### 聚合和领域服务和仓储服务的比较
+
+| 特性 | **聚合(Aggregate)** | **领域服务(Domain Service)** | **仓储(Repository)** |
+| ------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
+| **本质** | 相关**实体和值对象的组合**,以“聚合根”为唯一访问入口 | 无状态的**业务逻辑单元**,封装**跨实体 / 跨聚合**规则 | 抽象的数据访问接口,隐藏底层存储细节,为聚合**提供持久化能力** |
+| **状态** | **有状态**——内部维护数据与不变式 | **无状态**——仅暴露行为 | **无业务状态**;实现层可能有缓存,但对外看作无状态 |
+| **职责** | 1. 内部一致性2. 定义事务边界3. 提供领域行为(`order.pay()` 等) | 1. 承载跨实体规则2. 协调多个聚合完成业务动作 | 1. 加载 / 保存聚合根2. 把 PO ↔️ Entity 映射3. 屏蔽 SQL/ORM/缓存等技术细节 |
+| **边界** | *聚合边界*:内部操作要么全部成功要么全部失败 | 无一致性边界,仅调用聚合或仓储 | *持久化边界*:一次操作针对一个聚合;不负责业务事务(由应用层控制) |
+| **典型用法** | `Order.addItem()`,`Order.cancel()` | `PricingService.calculate(...)`,`InventoryService.reserveStock(...)` | `orderRepository.findById(id)`,`orderRepository.save(order)` |
+
+**自己总结:**领域服务纯写业务逻辑并注入仓储服务;
+
+仓储服务只写接口,规定一个具体的'动作';
+
+然后基础设施层中子类实现该仓储接口,并注入若干Dao,一个'动作'可能调用多个Dao来实现;
+
+Dao直接与数据库打交道,实现增删查改。
+
+
diff --git a/自学/Java笔记本.md b/自学/Java笔记本.md
index aea702b..067e588 100644
--- a/自学/Java笔记本.md
+++ b/自学/Java笔记本.md
@@ -11,14 +11,14 @@ Intellij Ideav创建Java项目:
IDEA快捷键:
-| `Ctrl + Alt + L` | 格式化代码 |
-| ---------------- | ------------------------- |
-| `Ctrl + /` | 注释/取消注释当前行 |
-| `Ctrl + D` | 复制当前行或选中的代码块 |
-| `Ctrl + Y` | 删除当前行 |
-| `Ctrl + N` | 查找类 |
-| `shift+shift` | 在文件中查找代码 |
-| `alt+回车` | service接口类跳转到实现类 |
+| `Ctrl + L` | 格式化代码 |
+| ------------- | ------------------------- |
+| `Ctrl + /` | 注释/取消注释当前行 |
+| `Ctrl + D` | 复制当前行或选中的代码块 |
+| `Ctrl + Y` | 删除当前行 |
+| `Ctrl + N` | 查找类 |
+| `shift+shift` | 在文件中查找代码 |
+| `alt+回车` | service接口类跳转到实现类 |
@@ -1859,6 +1859,8 @@ public @interface MyAnnotation {
@Target(ElementType.TYPE) //类上的注解(包含类、接口、枚举等类型)
+@Target(ElementType.FIELD) //字段上的注解
+
**简化使用**:如果注解中只有一个属性需要设置,而且该属性名为 `value`,则在使用时可以省略属性名
diff --git a/自学/力扣Hot 100题.md b/自学/力扣Hot 100题.md
index 0b8edab..b726644 100644
--- a/自学/力扣Hot 100题.md
+++ b/自学/力扣Hot 100题.md
@@ -1016,50 +1016,43 @@ Integer top = stack.pop(); // 出栈
- `offerFirst(E e)`:**在队头插入元素**,返回 `true` 或 `false` 表示是否成功。
- `peekFirst()`:查看队头元素,不移除;队列为空返回 `null`。
- `pollFirst()`:移除并返回队头元素;队列为空返回 `null`。
+- `poll()` :移除并返回队头元素
*在队尾操作*
- `offerLast(E e)`:在队尾插入元素,返回 `true` 或 `false` 表示是否成功。
+- `offer(E e)` : 把元素放到 队尾
- `peekLast()`:查看队尾元素,不移除;队列为空返回 `null`。
- `pollLast()`:移除并返回队尾元素;队列为空返回 `null`。
-
-
-*添加元素*:调用 `offer(e)` 时,实际上是调用 `offerLast(e)`,即在**队尾**添加元素。push(e)` ⇒ 等价于 `addFirst(e)
-
-*删除或查看元素*:调用`poll()` 时,则是调用 `pollFirst()`,即在队头移除元素;同理, `peek()` 则是查看队头元素。
-
```java
import java.util.Deque;
import java.util.ArrayDeque;
-public class DequeExample {
+public class DequeExampleSafe {
public static void main(String[] args) {
// 使用 ArrayDeque 实现双端队列
Deque deque = new ArrayDeque<>();
- // 在队列头部添加元素
- deque.addFirst(10);
- // 在队列尾部添加元素
- deque.addLast(20);
- // 在队列头部插入元素(和 addFirst 类似,但失败时不会抛异常)
- deque.offerFirst(5);
- // 在队列尾部插入元素
- deque.offerLast(30);
+ /* ========= 在队列两端“安全”地添加元素 ========= */
+ deque.offerFirst(10); // 队头 ← 10
+ deque.offerLast(20); // 20 ← 队尾
+ deque.offerFirst(5); // 队头 ← 5,10,20
+ deque.offerLast(30); // 队尾 → 5,10,20,30
System.out.println("双端队列内容:" + deque);
- // 查看队头和队尾元素,不移除
- int first = deque.peekFirst();
- int last = deque.peekLast();
+ /* ========= “安全”地查看队头/队尾 ========= */
+ Integer first = deque.peekFirst(); // 队头元素(5)
+ Integer last = deque.peekLast(); // 队尾元素(30)
System.out.println("队头元素:" + first);
System.out.println("队尾元素:" + last);
- // 从队头移除元素
- int removedFirst = deque.removeFirst();
+ /* ========= “安全”地移除队头/队尾 ========= */
+ Integer removedFirst = deque.pollFirst(); // 移除并返回队头(5)
System.out.println("移除队头元素:" + removedFirst);
- // 从队尾移除元素
- int removedLast = deque.removeLast();
+
+ Integer removedLast = deque.pollLast(); // 移除并返回队尾(30)
System.out.println("移除队尾元素:" + removedLast);
System.out.println("双端队列最终内容:" + deque);
@@ -1069,6 +1062,16 @@ public class DequeExample {
+**栈和双端队列的对应关系**
+
+栈只有队头!
+
+* 添加元素:push(e) ⇒ addFirst(e) (安全版:offerFirst(e))
+* 删除元素:pop() ⇒ removeFirst() (安全版:pollFirst())
+* 查看栈顶:peek() ⇒ peekFirst()
+
+
+
### Iterator
- **`HashMap`、`HashSet`、`ArrayList` 和 `PriorityQueue`** 都实现了 `Iterable` 接口,支持 `iterator()` 方法。
@@ -1219,6 +1222,8 @@ public void bubbleSort(int[] arr){
### 归并排序
+#### **数组归并排序**
+
**基本思想:**
将待排序的数组视为多个有序子表,每个子表的长度为 1,通过两两归并逐步合并成一个有序数组。
@@ -1288,6 +1293,51 @@ public class MergeSort {
+#### 链表归并排序
+
+```java
+// 简易链表归并排序示意
+ListNode sortList(ListNode head) {
+ if (head == null || head.next == null) return head;
+ // ① 快慢指针找中点并断链
+ ListNode slow = head, fast = head.next;
+ while (fast != null && fast.next != null) {
+ slow = slow.next;
+ fast = fast.next.next;
+ }
+ ListNode mid = slow.next;
+ slow.next = null;
+ // ② 递归排序左右两段
+ ListNode left = sortList(head);
+ ListNode right = sortList(mid);
+ // ③ 合并
+ return merge(left, right);
+}
+
+ListNode merge(ListNode a, ListNode b) {
+ ListNode dummy = new ListNode(-1), p = dummy;
+ while (a != null && b != null) {
+ if (a.val <= b.val) { p.next = a; a = a.next; }
+ else { p.next = b; b = b.next; }
+ p = p.next;
+ }
+ p.next = (a != null ? a : b);
+ return dummy.next;
+
+}
+```
+
+**快慢指针找 “中点” 和找 “环” 的出发点为什么会不一样?**
+
+| 目标 | 常见写法 | **为什么这么设** | 若改成另一种写法会怎样 |
+| -------------------------------------- | ------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
+| **拆链/找中点**(归并排序、回文检测等) | `slow = head`
`fast = head.next` | - **偶数长度更均匀**: *len = 4* → 最终 `slow` 停在第 2 个结点(左半长 2,右半长 2)- `mid = slow.next` 一定非 null(当链长 ≥ 2),递归不会拿到空指针 | - `fast = head` 时 *len = 4* → `slow` 停在第 3 个结点(左半长 3,右半长 1),越分越偏; *len = 2* → 左 1 右 0,也能跑,但更不平衡 |
+| **检测环** | `slow = head`
`fast = head` | - 只要两指针步幅不同,就会在环内相遇;- 起点放哪都行,写成同一起点最直观,也少一次空指针判断 | 如果写成 `fast = head.next` 也能检测环,但需先判断 `head.next` 是否为空,代码更啰嗦;并且两指针初始就“错开一步”,相遇时步数不同,求环长或入环点时要再做偏移 |
+
+总之:自己模拟一遍,奇数和偶数的情况。
+
+
+
### 数组排序
默认升序:
@@ -1676,7 +1726,6 @@ public void inOrderTraversalIterative(TreeNode root, List list) {
curr = curr.right;
}
}
-
```
*迭代法前序*
diff --git a/自学/拼团交易系统.md b/自学/拼团交易系统.md
index 745a4af..d9bc820 100644
--- a/自学/拼团交易系统.md
+++ b/自学/拼团交易系统.md
@@ -105,7 +105,7 @@ crowd_tags_job 人群标签任务
**(二)参与拼团表**
-group_buy_account 拼团账户
+**group_buy_account 拼团账户**
| 字段名 | 说明 |
| --------------------- | ------------ |
@@ -117,45 +117,50 @@ group_buy_account 拼团账户
| create_time | 创建时间 |
| update_time | 更新时间 |
-group_buy_order 用户拼单
+**group_buy_order 用户拼单**
-| 字段名 | 说明 |
-| ---------------------- | ------------------------ |
-| id | 自增ID |
-| activity_id | 活动ID |
-| group_order_id | 拼单ID 【多少人参与】 |
-| group_order_start_time | 拼单开始时间 |
-| group_order_end_time | 拼单结束时间 |
-| source | 来源 |
-| channel | 渠道 |
-| goods_id | 商品ID |
-| original_price | 原始价格 |
-| deduction_price | 抵扣价格(各类优惠加成) |
-| pay_amount | 实际支付价格 |
-| target_count | 目标数量 |
-| complete_count | 完成数量 |
-| status | 状态(拼单中/完成/失败) |
-| notify_url | 回调接口 |
-| create_time | 创建时间 |
-| update_time | 更新时间 |
+一条记录 = 一个拼团**团队**(`team_id` 唯一)
-group_buy_order_list 用户拼单明细
+| 字段名 | 说明 |
+| --------------- | -------------------------------- |
+| id | 自增ID |
+| team_id | 拼单组队ID |
+| activity_id | 活动ID |
+| source | 渠道 |
+| channel | 来源 |
+| original_price | 原始价格 |
+| deduction_price | 折扣金额 |
+| pay_price | 支付价格 |
+| target_count | 目标数量 |
+| complete_count | 完成数量 |
+| status | 状态(0-拼单中、1-完成、2-失败) |
+| create_time | 创建时间 |
+| update_time | 更新时间 |
-| 字段名 | 说明 |
-| -------------- | ---------------------- |
-| id | |
-| activity_id | 活动ID |
-| group_order_id | 拼单ID |
-| user_id | 用户id |
-| user_type | 团长/团员 |
-| source | 来源 |
-| channel | 渠道 |
-| goods_id | 商品ID |
-| out_trade_no | 外部交易单号,唯一幂等 |
-| create_time | 创建时间 |
-| update_time | 更新时间 |
+**group_buy_order_list 用户拼单明细**
-notify_task 回调任务
+一条记录 = **某用户**在该团队里锁的一笔单
+
+| 字段名 | 说明 |
+| --------------- | ------------------------------------ |
+| id | 自增ID |
+| user_id | 用户ID |
+| team_id | 拼单组队ID |
+| order_id | 订单ID |
+| activity_id | 活动ID |
+| start_time | 活动开始时间 |
+| end_time | 活动结束时间 |
+| goods_id | 商品ID |
+| source | 渠道 |
+| channel | 来源 |
+| original_price | 原始价格 |
+| deduction_price | 折扣金额 |
+| status | 状态;0 初始锁定、1 消费完成 |
+| out_trade_no | 外部交易单号(确保外部调用唯一幂等) |
+| create_time | 创建时间 |
+| update_time | 更新时间 |
+
+**notify_task 回调任务**
| 字段名 | 说明 |
| -------------- | ---------------------------------- |
@@ -278,8 +283,54 @@ EndNode.apply() → 组装结果并返回 TrialBalanceEntity
+## 拼团交易锁单
+
+
+
+下单到支付中间有一个流程,即锁单,比如淘宝京东中,在这个环节(限定时间内)选择使用优惠券、京豆等,可以得到优惠价,再进行支付;拼团场景同理,先加入拼团,进行锁单,然后优惠试算,最后才付款。
+
+
+
## 收获
+### 实体对象
+
+实体是指具有唯一标识的业务对象。
+
+在 DDD 分层里,**Domain Entity ≠ 数据库 PO**。
+在 `edu.whut.domain.*.model.entity` 包下放的是**纯粹的业务对象**,它们只表达业务语义(团队 ID、活动时间、优惠金额……),对「数据持久化细节」保持**无感知**。因此它们看起来“字段不全”是正常的:
+
+- 它们不会带 `@TableName` / `@TableId` 等 MyBatis-Plus 注解;
+- 也不会出现数据库的技术字段(`id`、`create_time`、`update_time`、`status` 等);
+- 只保留聚合根真正**需要的**业务属性与行为。
+
+```java
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class PayActivityEntity {
+
+ /** 拼单组队ID */
+ private String teamId;
+ /** 活动ID */
+ private Long activityId;
+ /** 活动名称 */
+ private String activityName;
+ /** 拼团开始时间 */
+ private Date startTime;
+ /** 拼团结束时间 */
+ private Date endTime;
+ /** 目标数量 */
+ private Integer targetCount;
+
+}
+```
+
+这个也是实体对象,因为多个字段的组合:teamId和activityId能唯一标识这个实体。
+
+
+
### 模板方法
**核心思想**:
@@ -366,6 +417,229 @@ public class Demo {
+### 责任链
+
+应用场景:日志系统、审批流程、权限校验——任何需要将请求按阶段传递、并由某一环节决定是否继续或终止处理的地方,都非常适合职责链模式。
+
+#### 单例链
+
+典型的责任链模式要点:
+
+- **解耦请求发送者和处理者**:调用者只持有链头,不关心中间环节。
+- **动态组装**:通过 `appendNext` 可以灵活地增加、删除或重排链上的节点。
+- **可扩展**:新增处理逻辑只需继承 `AbstractLogicLink` 并实现 `apply`,不用改动已有代码。
+
+接口定义:`ILogicChainArmory` 提供添加节点方法和获取节点
+
+```java
+//定义了责任链的组装接口:
+public interface ILogicChainArmory {
+
+ ILogicLink next(); //在当前节点中获取下一个节点
+
+ ILogicLink appendNext(ILogicLink next); //把下一个处理节点挂接上来
+
+}
+```
+
+`ILogicLink` 继承自 `ILogicChainArmory`,并额外声明了核心方法 `apply`
+
+```java
+public interface ILogicLink extends ILogicChainArmory {
+
+ R apply(T requestParameter, D dynamicContext) throws Exception; //处理请求
+
+}
+```
+
+抽象基类:`AbstractLogicLink`
+
+```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;
+ }
+
+ protected R next(T requestParameter, D dynamicContext) throws Exception {
+ return next.apply(requestParameter, dynamicContext); //交给下一节点处理
+ }
+
+}
+```
+
+子类只需继承它,重写 `apply(...)`,在合适的条件下要么直接处理并返回,要么调用 `next(requestParameter, dynamicContext)` 继续传递。
+
+**使用示例:**
+
+```java
+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);
+ }
+}
+
+public class LoggingLink extends AbstractLogicLink {
+ @Override
+ public Response apply(Request req, Context ctx) throws Exception {
+ System.out.println("Request received: " + req);
+ Response resp = next(req, ctx);
+ System.out.println("Response sent: " + resp);
+ return resp;
+ }
+}
+
+// 组装责任链 放工厂类factory中实现
+ILogicLink chain =
+ new AuthLink()
+ .appendNext(new LoggingLink())
+ .appendNext(new BusinessLogicLink());
+
+//客户端使用
+Request req = new Request(...);
+Context ctx = new Context(...);
+Response resp = chain.apply(req, ctx);
+```
+
+示例图:
+
+```text
+AuthLink.apply
+ └─▶ LoggingLink.apply
+ └─▶ BusinessLogicLink.apply
+ └─▶ 返回 Response
+```
+
+这种模式链上的每个节点都手动 `next()`到下一节点。
+
+
+
+#### 多例链
+
+```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
+
+```
+
+链头拿着“游标”一个个跑,节点只告诉“命中 / 未命中”。
+
+
+
### 规则树流程

@@ -502,6 +776,8 @@ public class DiscountContext {
### 多线程异步调用
+如果某任务比较耗时(如加载大量数据),可以考虑开多线程异步调用。
+
```java
// Runnable ➞ 只能 run(),没有返回值
public interface Runnable {
@@ -551,7 +827,7 @@ public class SimpleAsyncDemo {
// 主线程可以先做别的事…
System.out.println("主线程正在做其他事情…");
- // ③ 在需要的时候再获取结果(可加超时)
+ // 在需要的时候再获取结果(可加超时)
String result1 = future1.get(1, TimeUnit.SECONDS); //设置超时时间1秒
String result2 = future2.get(); //无超时时间
@@ -571,3 +847,96 @@ public class SimpleAsyncDemo {
}
```
+
+
+### 动态配置(热更新)
+
+**注解标记**
+用 `@DCCValue("key:default")` 标注需要动态注入的字段,指定对应的 Redis Key(带前缀)及默认值。
+
+```java
+// 标记要动态注入的字段
+@Retention(RUNTIME) @Target(FIELD)
+public @interface DCCValue {
+ String value(); // "key:default"
+}
+```
+
+```java
+// 业务使用示例
+@Service
+public class MyFeature {
+ @DCCValue("myFlag:0")
+ private String myFlag;
+ public boolean enabled() { return "1".equals(myFlag); }
+}
+```
+
+**启动时注入**
+实现一个 `BeanPostProcessor`,在每个 Spring Bean 初始化后:
+
+- 扫描带 `@DCCValue` 的字段;
+- 拼出完整 Redis Key(如 `dcc_prefix_key`),若不存在则写入默认值,否则读最新值;
+- 反射把值注入到该 Bean 的私有字段;
+- 将 `(redisKey → Bean 实例)` 记录到内存映射,用于后续热更新。
+
+```java
+@Override
+public Object postProcessAfterInitialization(Object bean, String name) {
+ Class> cls = AopUtils.isAopProxy(bean)
+ ? AopUtils.getTargetClass(bean)
+ : bean.getClass();
+ for (Field f : cls.getDeclaredFields()) {
+ DCCValue dv = f.getAnnotation(DCCValue.class);
+ if (dv==null) continue;
+ String[] p = dv.value().split(":");
+ String key = PREFIX + p[0], defaultValue = p[1];
+ RBucket bucket = redis.getBucket(key);
+ String val = bucket.isExists() ? bucket.get() : defaultValue;
+ bucket.trySet(defaultValue); //同步redis内容
+ injectField(bean, f, val); //反射注入
+ beans.put(key, bean);
+ }
+ return bean;
+ }
+```
+
+**运行时热更新**
+
+- 在同一个组件里,订阅一个 Redis Topic(频道),比如 `"dcc_update"`;
+
+- 外部调用发布接口 `PUBLISH dcc_update "key,newValue"`;
+
+ ```java
+ //更新配置
+ @GetMapping("/dcc/update")
+ public void update(@RequestParam String key, @RequestParam String value) {
+ dccTopic().publish(key + "," + value);
+ }
+ ```
+
+- 订阅者收到后:
+
+ 1. 同步把新值写回 Redis;
+ 2. 从映射里取出对应 Bean,反射更新它的字段。
+
+```java
+// 发布/订阅频道
+ @Bean
+ public RTopic dccTopic() {
+ RTopic t = redis.getTopic("dcc_update");
+ t.addListener(String.class, (c,msg)->{
+ String[] a = msg.split(",");
+ String key = PREFIX + a[0], val = a[1];
+ RBucket bucket = redis.getBucket(key);
+ if (!bucket.isExists()) return;
+ bucket.set(val);
+ Object bean = beans.get(key);
+ if (bean!=null) injectField(bean, a[0], val);
+ });
+ return t;
+ }
+```
+
+
+
diff --git a/自学/草稿.md b/自学/草稿.md
index 227d799..7fec74f 100644
--- a/自学/草稿.md
+++ b/自学/草稿.md
@@ -1,159 +1,19 @@
-### 一、策略模式 (Strategy)
+### 聚合 vs 领域服务 vs 仓储 —— 对比一览
-**核心思想**:
- 把可互换的算法/行为抽成独立策略类,运行时由“上下文”对象选择合适的策略;对调用方来说,只关心统一接口,而非具体实现。
+| 特性 | **聚合(Aggregate)** | **领域服务(Domain Service)** | **仓储(Repository)** |
+| ------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
+| **本质** | 相关实体和值对象的**组合**,以“聚合根”为唯一访问入口 | 无状态的业务逻辑单元,封装**跨实体 / 跨聚合**规则 | 抽象的数据访问接口,隐藏底层存储细节,为聚合**提供持久化能力** |
+| **状态** | **有状态**——内部维护数据与不变式 | **无状态**——仅暴露行为 | **无业务状态**;实现层可能有缓存,但对外看作无状态 |
+| **职责** | 1. 内部一致性2. 定义事务边界3. 提供领域行为(`order.pay()` 等) | 1. 承载跨实体规则2. 协调多个聚合完成业务动作 | 1. 加载 / 保存聚合根2. 把 PO ↔️ Entity 映射3. 屏蔽 SQL/ORM/缓存等技术细节 |
+| **边界** | *聚合边界*:内部操作要么全部成功要么全部失败 | 无一致性边界,仅调用聚合或仓储 | *持久化边界*:一次操作针对一个聚合;不负责业务事务(由应用层控制) |
+| **典型用法** | `Order.addItem()`,`Order.cancel()` | `PricingService.calculate(...)`,`InventoryService.reserveStock(...)` | `orderRepository.findById(id)`,`orderRepository.save(order)` |
-```
-┌───────────────┐
-│ Client │
-└─────▲─────────┘
- │ has-a
-┌─────┴─────────┐ implements
-│ Context │────────────┐ ┌──────────────┐
-│ (使用者) │ strategy └─▶│ Strategy A │
-└───────────────┘ ├──────────────┤
- │ Strategy B │
- └──────────────┘
-```
+#### 快速记忆
-#### Demo:支付策略(Java)
+- **聚合**:有数据 + 行为,是“业务发动机”
+- **领域服务**:无数据,专管“发动机之间的协作”
+- **仓储**:无业务规则,只负责把“发动机”**存取**到持久化介质
-```java
-// 1. 抽象策略
-public interface PayStrategy {
- void pay(int cents);
-}
+这样三者的定位就清晰了:
-// 2. 具体策略
-public class AliPay implements PayStrategy {
- public void pay(int cents) { System.out.println("Alipay ¥" + cents / 100.0); }
-}
-public class WxPay implements PayStrategy {
- public void pay(int cents) { System.out.println("WeChat Pay ¥" + cents / 100.0); }
-}
-
-// 3. 上下文
-public class PaymentService {
- private final PayStrategy strategy;
- public PaymentService(PayStrategy strategy) { this.strategy = strategy; }
- public void checkout(int cents) { strategy.pay(cents); }
-}
-
-// 4. 运行时选择策略
-public class Demo {
- public static void main(String[] args) {
- PaymentService ps1 = new PaymentService(new AliPay());
- ps1.checkout(2599); // Alipay ¥25.99
-
- PaymentService ps2 = new PaymentService(new WxPay());
- ps2.checkout(4999); // WeChat Pay ¥49.99
- }
-}
-```
-
-**要点**
-
-- **开放封闭**:新增 PayPal 只需实现 `PayStrategy`,无须改 `PaymentService`。
-- **运行期切换**:可根据配置、用户偏好等动态注入不同策略。
-
-------
-
-### 二、模板方法模式 (Template Method)
-
-**核心思想**:
- 在抽象父类中定义**算法骨架**(固定执行顺序),把某些可变步骤留给子类重写;调用方只用模板方法,保证流程一致。
-
-```
- Client ───▶ AbstractClass
- ├─ templateMethod() ←—— 固定流程
- │ step1()
- │ step2() ←—— 抽象,可变
- │ step3()
- └─ hookMethod() ←—— 可选覆盖
- ▲
- │ extends
- ┌──────────┴──────────┐
- │ ConcreteClassA/B… │
-```
-
-#### Demo:弹窗加载流程(Java)
-
-```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`)。
-
-------
-
-### 关键区别 & 组合用法
-
-| | **策略模式** | **模板方法模式** |
-| ---------------- | ---------------------------------- | ---------------------------------------- |
-| **目的** | **横向**扩展——允许算法**并列互换** | **纵向**复用——抽取算法**骨架**,固定顺序 |
-| **实现方式** | 组合 + 接口 | 继承 + 抽象父类 |
-| **行为选择时机** | 运行时由外部注入 | 编译期由继承确定 |
-| **常组合** | 与 **工厂模式**配合选择策略 | 与 **钩子方法**、**回调**一起用 |
-
-在实际项目中,两者经常**组合**:
-
-> 折扣计算 **Strategy** → 公共过滤 & 日志 **Template Method** → Spring 容器负责策略注册/发现。
-
-这样即可同时获得“纵向流程复用”+“横向算法可插拔”的双重优势。
+> “**聚合**管状态,**领域服务**管跨聚合业务,**仓储**管存取。”