第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()

  1. repository.queryStrategyEntityByStrategyId(100003L) 查出策略实体
  2. strategy.ruleModels() 拿到策略 100003 配置的规则数组,假设是 ["rule_blacklist", "rule_weight"]
  3. 按顺序从 logicChainGroup(那个 Spring 注入的 Map)取出链节点并串联:
  rule_blacklist链 → rule_weight链 → default链(末尾自动挂兜底)
  1. 返回链头 = 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 过滤器(旧的 RuleBackListLogicFilterRuleWeightLogicFilter 已被删除)。

而 DefaultLogicFactory + ILogicFilter + RuleLockLogicFilter 仍然服务于"抽奖中/抽奖后"的规则:

它们各自的作用:

① ILogicFilter —— 抽奖中/后规则的统一接口(filter() 方法)

② RuleLockLogicFilterrule_lock —— 抽奖中规则的具体实现

  • 逻辑:用户抽奖次数 ≥ 规则限定次数才放行该奖品,否则拦截走兜底
  • 这是"抽奖前"责任链做不了的事——因为它依赖"已经抽到的 awardId",必须在抽奖后才能校验

③ DefaultLogicFactory —— 过滤器工厂

  • 启动时通过 @LogicStrategy 注解,把所有 filter 收集进 logicFilterMap
  • LogicModel 枚举里现在只剩 RULE_LOCK(center) 和 RULE_LUCK_AWARD(after)——正好印证:黑名单和权重已经从这里"搬走"到责任链了

一总结

责任链没有取代过滤器,而是分工:责任链管"抽奖前"(黑名单/权重,决定抽哪个奖池),过滤器管"抽奖中/后"(次数锁/幸运奖,决定抽到的奖品能不能给)。DefaultLogicFactoryILogicFilterRuleLockLogicFilter 依然是抽奖中后阶段的核心,删不得。

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 里的 AbstractListAbstractMap 都是这么设计的。

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() 的意思是:每个具体链(子类)都必须提供自己的规则标识,父类定义规范,子类填实现。

posted @ 2026-07-21 15:44  超级无敌大帅比  阅读(1)  评论(0)    收藏  举报