第7节:责任链模式处理抽奖规则
本节课主要学习了责任链,在抽奖前进行过滤
-
本节课新写的接口
ILogicChainArmory (定义 next / appendNext 用于装配 )
↑ 继承
ILogicChain (定义 logic 制定后面的继承类要实现) ← 所以 ILogicChain 实际有 3 个方法
↑ 实现 implements
AbstractLogicChain (abstract 抽象类)
↑ 继承 extends
BackListLogicChain / RuleWeightLogicChain / DefaultLogicChain (具体类)
-
责任链调用逻辑 以黑名单为例子
ILogicChain logicChain = defaultChainFactory.openLogicChain(100003L);
Integer awardId = logicChain.logic("user001", 100003L);
第 1 步:openLogicChain(100003L) —— 组装责任链
进 DefaultChainFactory.openLogicChain():
repository.queryStrategyEntityByStrategyId(100003L)查出策略实体strategy.ruleModels()拿到策略 100003 配置的规则数组,假设是["rule_blacklist", "rule_weight"]- 按顺序从
logicChainGroup(那个 Spring 注入的 Map)取出链节点并串联:
rule_blacklist链 → rule_weight链 → default链(末尾自动挂兜底)
- 返回链头 =
BackListLogicChain(黑名单链)
第 2 步:logicChain.logic("user001", 100003L) —— 责任链执行
① 进入黑名单链 BackListLogicChain.logic():
String ruleValue = repository.queryStrategyRuleValue(100003L, "rule_blacklist");
// 例如 ruleValue = "101:user001,user002,user003"
String[] split = ruleValue.split(":"); // ["101", "user001,user002,user003"]
Integer awardId = 101; // 冒号前 = 黑名单固定奖品ID
String[] blackIds = split[1].split(","); // [user001, user002, user003]
- 遍历黑名单,判断
"user001"在不在里面 - 命中!
user001是黑名单用户 → 直接return 101,责任链到此终止,后面的权重链、默认链根本不执行
如果 user001 不在黑名单 → 执行 return next().logic(...) 把请求传给下一个链(权重链),以此类推,直到 default 兜底链一定返回一个奖品。
总结:
组装链条 → 从黑名单链开始 → 命中黑名单返回固定奖品(终止) / 不命中则
next()传递 → 权重链 → 默认兜底链。这个测试里user001命中黑名单,直接返回奖品 101。
-
问题
1.开了责任链后,DefaultLogicFactory / RuleLockLogicFilter / ILogicFilter 还有用吗?
答:
有用,而且很关键。 它们负责的是不同阶段的规则。这是理解整个抽奖设计的核心——规则分三个阶段:
| 阶段 | 用什么机制 | 负责的规则 | 代码位置 |
|---|---|---|---|
| 抽奖前 (before) | ✅ 责任链 (Chain) | 黑名单 rule_blacklist、权重 rule_weight |
rule/chain/ |
| 抽奖中 (center) | ✅ 过滤器 (Filter) | 次数锁 rule_lock |
rule/filter/ |
| 抽奖后 (after) | ✅ 过滤器 (Filter) | 幸运奖 rule_luck_award |
rule/filter/ |
为什么责任链只替换了"抽奖前"?
看 AbstractRaffleStrategy 的主流程就明白了:
// 抽奖前:责任链拿到 awardId
ILogicChain logicChain = defaultChainFactory.openLogicChain(strategyId);
Integer awardId = logicChain.logic(userId, strategyId);
// 抽奖后半段:还是用 filter 过滤器!
StrategyAwardRuleModelVO ruleModelVO = repository.queryStrategyAwardRuleModelVO(strategyId, awardId);
RuleActionEntity centerEntity = this.doCheckRaffleCenterLogic(...); // ← 这里用 filter
责任链替换的只是"抽奖前"那段原来乱糟糟的 if-else 过滤器(旧的 RuleBackListLogicFilter、RuleWeightLogicFilter 已被删除)。
而 DefaultLogicFactory + ILogicFilter + RuleLockLogicFilter 仍然服务于"抽奖中/抽奖后"的规则:
它们各自的作用:
① ILogicFilter —— 抽奖中/后规则的统一接口(filter() 方法)
② RuleLockLogicFilter(rule_lock) —— 抽奖中规则的具体实现
- 逻辑:用户抽奖次数 ≥ 规则限定次数才放行该奖品,否则拦截走兜底
- 这是"抽奖前"责任链做不了的事——因为它依赖"已经抽到的 awardId",必须在抽奖后才能校验
③ DefaultLogicFactory —— 过滤器工厂
- 启动时通过
@LogicStrategy注解,把所有 filter 收集进logicFilterMap LogicModel枚举里现在只剩RULE_LOCK(center) 和RULE_LUCK_AWARD(after)——正好印证:黑名单和权重已经从这里"搬走"到责任链了
一总结
责任链没有取代过滤器,而是分工:责任链管"抽奖前"(黑名单/权重,决定抽哪个奖池),过滤器管"抽奖中/后"(次数锁/幸运奖,决定抽到的奖品能不能给)。
DefaultLogicFactory、ILogicFilter、RuleLockLogicFilter依然是抽奖中后阶段的核心,删不得。
2.AbstractLogicChain ILogicChain ILogicChainArmory 为什么抽象类 AbstractLogicChain继承了ILogicChain 但是不用重写logic方法?她们这样定义的好处是什么?
为什么 AbstractLogicChain 不用重写 logic()?
答:
核心规则:抽象类可以不实现接口的全部方法,没实现的方法会"自动变成抽象方法",留给具体子类去实现。
看继承关系:
ILogicChainArmory (定义 next / appendNext)
↑ 继承
ILogicChain (定义 logic) ← 所以 ILogicChain 实际有 3 个方法
↑ 实现 implements
AbstractLogicChain (abstract 抽象类)
↑ 继承 extends
BackListLogicChain / RuleWeightLogicChain / DefaultLogicChain (具体类)
AbstractLogicChain 需要实现 ILogicChain 的 3 个方法:next()、appendNext()、logic()。它做了选择:
public abstract class AbstractLogicChain implements ILogicChain {
private ILogicChain next;
@Override
public ILogicChain next() { return next; } // ✅ 实现了
@Override
public ILogicChain appendNext(ILogicChain next) { // ✅ 实现了
this.next = next;
return next;
}
// logic() 没写 → 因为是抽象类,允许不实现,它自动"保持抽象"
protected abstract String ruleModel(); // 还额外加了个抽象方法
}
-
next()/appendNext()→ 所有链的逻辑都一样(就是维护 next 指针),所以父类统一实现一次,子类白捡。 logic()→ 每个链的逻辑都不同(黑名单查黑名单、权重算积分、默认随机),父类没法写,所以留空不实现,强制每个子类自己写。
因为类是
abstract的,编译器允许它"欠着"logic()不实现——这个"欠账"由继承它的具体子类还上。如果AbstractLogicChain不是 abstract,那就必须实现 logic(),否则编译报错。
这样设计(接口拆分 + 抽象类)好在哪?
① 抽取共性,消除重复代码(模板作用)
next()/appendNext() 三个具体链都需要,逻辑完全一样。放父类实现一次,三个子类就不用各写一遍。共性上提,个性下沉。
② 强制规范子类(抽象方法约束)
logic() 和 ruleModel() 是抽象的,任何子类都必须实现,否则编译不过。等于父类立了规矩:想当一个链,就必须提供"处理逻辑"和"规则标识"。防止有人漏写。
③ 接口拆成两个(ISP 接口隔离原则)
把接口拆成 ILogicChainArmory(装配:next/appendNext)+ ILogicChain(执行:logic),职责分开:
- 装配能力(怎么串成链)和 执行能力(链干什么)解耦
- 以后想单独扩展"装配逻辑"或"执行逻辑"互不影响
④ 面向接口编程,便于替换和扩展(OCP 开闭原则)
工厂里用的是 Map<String, ILogicChain> 和 ILogicChain 类型,不关心具体是哪个链。新增规则只要写个新子类继承 AbstractLogicChain,加个 @Component,完全不用改父类和工厂代码。
总结
AbstractLogicChain是抽象类,所以可以「实现公共的(next/appendNext),留空个性的(logic)」。好处是:公共逻辑只写一次、个性逻辑强制子类实现、接口按职责拆分——这正是"模板方法 + 责任链 + 面向接口"三种思想的组合,既减少重复又保证扩展性。
这其实是一个经典模式叫 骨架实现(Skeletal Implementation)——接口定标准,抽象类填公共骨架,具体类填差异。JDK 里的 AbstractList、AbstractMap 都是这么设计的。
3.语法
(1)public class DefaultChainFactory {
private final Map<String, ILogicChain> logicChainGroup;为什么这个要用final?
答:
final 表示这个引用一旦赋值就不能再改指向。它是构造器注入的:
public DefaultChainFactory(Map<String, ILogicChain> logicChainGroup, ...) {
this.logicChainGroup = logicChainGroup; // 只在构造时赋值一次
}
用 final 的三个理由:
- 不可变、更安全:Bean 初始化后这个 Map 引用就固定了,防止代码里手滑给它重新赋值(
logicChainGroup = xxx)编译期就报错。 - 线程安全语义:
final字段在构造完成后对所有线程可见,单例 Bean 多线程访问更放心。 - 表达"依赖不可变"的意图:这是 Spring 官方推荐的构造器注入写法——依赖注入进来后就不该变。
注意:
final只锁"引用不能变",Map 里的内容还是能增删的。这里正好——引用固定,内容由 Spring 装配。
(2) i String ruleModels = strategy.getRuleModels();
if (ruleModels == null || 0==ruleModels.length()) 为什么不用isempty?
答:
用 isEmpty() 完全可以,甚至更清晰。作者用 length()==0 只是习惯而已,不是必须。
(3)logicChainGroup.get(“default”); 这个是在什么时候注入的?
答:
Spring 有个特性:当你注入 Map<String, 某接口> 时,它会自动把容器里所有该接口类型的 Bean 收集成一个 Map,其中:
- key = Bean 的名字
- value = Bean 实例
而每个链的实现类上都标了 @Component("名字"):
@Component("default") // ← key 就是 "default"
public class DefaultLogicChain extends AbstractLogicChain { ... }
@Component("rule_blacklist") // ← key 是 "rule_blacklist"
public class BackListLogicChain ...
@Component("rule_weight") // ← key 是 "rule_weight"
public class RuleWeightLogicChain ...
注入时机:Spring 容器启动时,扫描到这 3 个 @Component,创建实例,然后在构造 DefaultChainFactory 时把它们打包成:
{
"default" : DefaultLogicChain 实例,
"rule_blacklist" : BackListLogicChain 实例,
"rule_weight" : RuleWeightLogicChain 实例
}
所以你运行时 logicChainGroup.get("default") 拿到的就是 DefaultLogicChain 实例——是 Spring 在启动阶段自动注入的,不用你手动 put。
一句话:
@Component("default")的名字 + Spring 的Map注入机制 = 自动装配。以后新增规则链,只要加个@Component("xxx"),Map 里自动就有了。
(4)protected的作用是什么
答:
protected 是 Java 的访问修饰符,控制"谁能访问这个成员"。Java 四种访问级别,从宽到窄:
| 修饰符 | 同类 | 同包 | 子类 | 其他 |
|---|---|---|---|---|
public |
✅ | ✅ | ✅ | ✅ |
protected |
✅ | ✅ | ✅(跨包也行) | ❌ |
| 默认(不写) | ✅ | ✅ | ❌ | ❌ |
private |
✅ | ❌ | ❌ | ❌ |
protected 的核心作用:允许子类访问,但对无关的外部类隐藏。
public abstract class AbstractLogicChain implements ILogicChain {
protected abstract String ruleModel(); // 子类必须实现,且能被子类访问
}
@Service
public class DefaultChainFactory {
protected IStrategyRepository repository; // 让子类(如果有)能直接用
}
用 protected 而不是 private 的意图:
- 这个成员/方法是给继承体系用的——子类需要访问或重写它。
- 但又不想让毫不相关的外部类随便调用(所以不用
public)。
在责任链这里,protected abstract String ruleModel() 的意思是:每个具体链(子类)都必须提供自己的规则标识,父类定义规范,子类填实现。

浙公网安备 33010602011771号