一、责任链模式概述
意图:为一系列处理对象创建一条链,使请求沿着链传递,直到有对象处理它或链尾。
别名:职责链、过滤链。
1.1 核心角色
- Handler(处理者接口)
定义处理请求的方法以及后续链的引用:
interface IHandler {
/// <summary>尝试处理请求,返回 true 表示已处理,false 则继续传递</summary>
bool Handle(EventData data);
void SetNext(IHandler next);
}
ConcreteHandler(具体处理者)
实现 Handle,对自己关心的请求进行处理,否则调用 next.Handle(data)。
Client(客户端)
将各个处理者按顺序 SetNext 串成链,然后发起统一请求:
headHandler.Handle(data);
1.2 UML 类图

二、Unity UI 点击拦截责任链
假设有一个复杂 UI 场景,需要按以下顺序处理点击事件:
- 遮挡层(
UIBlockerHandler)——如果在模态对话框/遮罩区,直接拦截,不再下发 - 交互层(
InteractionHandler)——如果点击可交互按钮或道具,执行对应回调 - 统计层(
LoggingHandler)——记录点击埋点,但不阻断事件
2.1 定义事件数据
public class UIEventData {
public PointerEventData PointerData;
// 可扩展:UI 组件引用、事件类型等
}
2.2 抽象基类
public abstract class UIEventHandler : IHandler {
private IHandler _next;
public void SetNext(IHandler next) {
_next = next;
}
public bool Handle(EventData data) {
if (CanHandle(data)) {
Process(data);
return true;
} else if (_next != null) {
return _next.Handle(data);
}
return false;
}
/// <summary>
///子类决定自己是否能处理该事件
///</summary>
protected abstract bool CanHandle(EventData data);
/// <summary>
///实际的处理逻辑
///</summary>
protected abstract void Process(EventData data);
}
2.3 具体处理者
// 1. 遮挡层:如果点击在 ModalMask 区域,拦截并不向下传递
public class UIBlockerHandler : UIEventHandler {
protected override bool CanHandle(EventData data) {
return data.PointerData.pointerEnter != null
&& data.PointerData.pointerEnter.CompareTag("ModalMask");
}
protected override void Process(EventData data) {
// 阻止进一步点击响应
Debug.Log("UIBlocker: click intercepted");
}
}
// 2. 交互层:只有可交互组件才处理
public class InteractionHandler : UIEventHandler {
protected override bool CanHandle(EventData data) {
return data.PointerData.pointerEnter != null
&& data.PointerData.pointerEnter.TryGetComponent<IInteractable>(out _);
}
protected override void Process(EventData data) {
var interactable = data.PointerData.pointerEnter.GetComponent<IInteractable>();
interactable.OnInteract();
}
}
// 3. 统计层:所有未被拦截的点击都记录日志
public class LoggingHandler : UIEventHandler {
protected override bool CanHandle(EventData data) {
// 统计层“万金油”,永远返回 true
return true;
}
protected override void Process(EventData data) {
AnalyticsManager.Instance.TrackEvent(
"UIClick",
new Dictionary<string, object> {
{ "object", data.PointerData.pointerEnter?.name ?? "none" }
}
);
}
}
2.4 组装链并发起请求
public class UIInputModule : MonoBehaviour, IPointerClickHandler {
private IHandler _chainHead;
void Awake() {
// 构建链:Blocker → Interaction → Logging
var blocker = new UIBlockerHandler();
var interaction = new InteractionHandler();
var logging = new LoggingHandler();
blocker.SetNext(interaction);
interaction.SetNext(logging);
_chainHead = blocker;
}
public void OnPointerClick(PointerEventData eventData) {
var data = new UIEventData { PointerData = eventData };
_chainHead.Handle(data);
}
}
- 点击时,只需调用
_chainHead.Handle(...) - 拦截后不会继续传递,保证了责任分离
三、网络消息处理责任链
同样思路可用于服务器或客户端消息分发。以简单 Auth → RateLimit → GameLogic 链为例:
// IMessage 为网络消息基类
public interface IMessageHandler {
bool Handle(Message msg);
void SetNext(IMessageHandler next);
}
public abstract class MessageHandler : IMessageHandler { /* 与 UIHandler 类似 */ }
public class AuthHandler : MessageHandler {
protected override bool CanHandle(Message msg) => !msg.IsAuthenticated;
protected override void Process(Message msg) {
// 校验 Token
msg.IsAuthenticated = AuthService.Validate(msg.Token);
}
}
public class RateLimitHandler : MessageHandler {
protected override bool CanHandle(Message msg) {
return RateLimiter.Allow(msg.UserId);
}
protected override void Process(Message msg) { /* 允许通过 */ }
}
public class GameLogicHandler : MessageHandler {
protected override bool CanHandle(Message msg) => true;
protected override void Process(Message msg) {
GameService.Process(msg);
}
}
// 组装链
IMessageHandler head = new AuthHandler();
head.SetNext(new RateLimitHandler());
head.SetNext(new GameLogicHandler());
head.Handle(incomingMessage);
四、高级话题与扩展
动态插拔
- 链可以在运行时
SetNext调整顺序或插入/移除节点;
- 链可以在运行时
并行链
- 对于“广播式”处理,可在每个节点
Process后同时通知多个分支;
- 对于“广播式”处理,可在每个节点
异步链
Handle返回Task<bool>,支持异步校验(如远程鉴权);
链式参数
EventData可包含状态Handled字段,让节点自行决定是否中断链;
public bool Handle(EventData data) { if (data.Handled) return false; if (CanHandle(data)) { Process(data); data.Handled = true; } return _next?.Handle(data) ?? false; }
五、性能与调试
链长度:链过长会增加请求延迟,需合理拆分和组合;
循环检测:防止在动态重组链时产生环形引用,造成无限循环;
日志:可在基类
Handle前后打印节点名称,快速定位处理流程:
Debug.Log($"[Handler] Trying {this.GetType().Name}");
六、注意事项
| 场景 | 陷阱 | 建议 |
|---|---|---|
| 链式循环 | 动态插拔不慎导致 A→B→A 循环,Handle 永不返回 | 在 SetNext 时检测已存在引用或单向图结构 |
| 节点吞掉所有请求 | 过于宽泛的 CanHandle 导致链无法继续 |
严格定义 CanHandle 准则,或在处理后显式调用 _next |
| 状态共享冲突 | 多个节点共享可变数据,链中途修改导致后续节点行为不一致 | EventData 设计为不可变或保存中间状态快照 |
| 测试困难 | 客户端直接使用具体链构造,难以隔离某个节点单元测试 | 将链构造逻辑提取到工厂或配置,测试时 Mock 各节点 |
七、小结
- 职责分离:每个处理者只关注单一责任,CanHandle 与 Process 清晰分离;
- 链式解耦:客户端只需持有链头
Handle,无需感知后续节点; - 动态灵活:可通过配置或脚本动态重组链,满足经营策略、A/B 测试等需求;
- 日志与监控:加入处理节点日志,定期查看链路性能和处理命中率;
- 与其他模式结合:可与 观察者 做事件分发,与 策略 做业务处理解耦。