一句话定位:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系------将这些对象连成一条链,沿着链传递请求,直到有一个对象处理它。
一、实战场景
公司内部系统里,报销审批是最典型的"链条式"业务。假设规则是:
- 500 元以内:直属主管审批即可;
- 500 ~ 5000 元:主管审批后,还需部门经理审批;
- 5000 ~ 50000 元:主管 → 部门经理 → 财务总监;
- 超过 50000 元:主管 → 部门经理 → 财务总监 → CEO。
如果把这些规则写成 if/else:
csharp
public ApprovalResult Approve(ExpenseClaim claim)
{
if (claim.Amount <= 500)
{
_supervisor.Approve(claim);
}
else if (claim.Amount <= 5000)
{
_supervisor.Approve(claim);
_manager.Approve(claim);
}
else if (claim.Amount <= 50000)
{
_supervisor.Approve(claim);
_manager.Approve(claim);
_finance.Approve(claim);
}
else
{
_supervisor.Approve(claim);
_manager.Approve(claim);
_finance.Approve(claim);
_ceo.Approve(claim);
}
return ...;
}
问题很明显:
- 审批规则的顺序和层级写死在业务代码里,规则一变(比如 5000 改成 8000)就要改这段逻辑;
- 审批人之间没有任何"传递"关系,每个层级都要业务层手动调用;
- 新加一个审批环节(比如"法务审批")要改这里,还要在 if/else 里再加分支。
责任链模式的解法:把每个审批人设计成"链上的一个节点",每个节点处理不了(或处理完自己那部分)就把请求传给下一个节点。业务层只需要把请求丢进链头,剩下的事由链自己完成。
二、设计思路
ExpenseClaim(请求:报销单)
│
▼
┌─────────┐ 通过/无权 ┌─────────┐ 通过/无权 ┌─────────┐
│ 主管节点 │──────────▶│ 部门经理 │──────────▶│ 财务总监 │──▶...──▶│ CEO │
└─────────┘ └─────────┘ └─────────┘ └─────┘
金额 ≤500 直接批 500~5000 批完即止 5000~50000
每个节点做两件事:
- 判断自己能不能处理(金额是否在权限内);
- 能处理就处理并结束;不能处理就交给下一个节点(也可以"处理完一部分再传递",取决于业务)。
三、代码实现(.NET 10 / C# 14)
3.1 请求模型
csharp
namespace Approvals.Expenses;
public sealed record ExpenseClaim(
string ClaimId,
string EmployeeName,
decimal Amount,
string Reason);
public enum ApprovalDecision
{
Approved,
Rejected
}
public sealed record ApprovalResult(ApprovalDecision Decision, string ApprovedBy, string Remark);
3.2 抽象处理器:链上节点
csharp
namespace Approvals.Expenses;
public abstract class ExpenseApprover
{
private ExpenseApprover? _next;
/// <summary>设置下一节点,返回下一节点便于链式构建</summary>
public ExpenseApprover SetNext(ExpenseApprover next)
{
_next = next;
return next;
}
protected ExpenseApprover? Next => _next;
/// <summary>模板方法:先试自己,处理不了就传下去</summary>
public async Task<ApprovalResult> HandleAsync(ExpenseClaim claim, CancellationToken ct = default)
{
var result = await TryApproveAsync(claim, ct);
// 自己处理不了(null 表示无权处理),沿链传递
if (result is null && _next is not null)
return await _next.HandleAsync(claim, ct);
return result ?? new ApprovalResult(ApprovalDecision.Rejected, "System", "无审批人可处理");
}
/// <summary>子类实现:能处理返回结果,不能处理返回 null</summary>
protected abstract Task<ApprovalResult?> TryApproveAsync(ExpenseClaim claim, CancellationToken ct);
}
3.3 具体节点:四级审批人
csharp
namespace Approvals.Expenses;
// 节点1:直属主管(500 元以内)
public sealed class SupervisorApprover(string name) : ExpenseApprover
{
protected override Task<ApprovalResult?> TryApproveAsync(ExpenseClaim claim, CancellationToken ct)
=> claim.Amount <= 500
? Task.FromResult<ApprovalResult?>(new(ApprovalDecision.Approved, name, "主管审批通过"))
: Task.FromResult<ApprovalResult?>(null);
}
// 节点2:部门经理(500 ~ 5000 元)
public sealed class DepartmentManagerApprover(string name) : ExpenseApprover
{
protected override Task<ApprovalResult?> TryApproveAsync(ExpenseClaim claim, CancellationToken ct)
=> claim.Amount is > 500 and <= 5000
? Task.FromResult<ApprovalResult?>(new(ApprovalDecision.Approved, name, "部门经理审批通过"))
: Task.FromResult<ApprovalResult?>(null);
}
// 节点3:财务总监(5000 ~ 50000 元)
public sealed class FinanceDirectorApprover(string name) : ExpenseApprover
{
protected override Task<ApprovalResult?> TryApproveAsync(ExpenseClaim claim, CancellationToken ct)
=> claim.Amount is > 5000 and <= 50000
? Task.FromResult<ApprovalResult?>(new(ApprovalDecision.Approved, name, "财务总监审批通过"))
: Task.FromResult<ApprovalResult?>(null);
}
// 节点4:CEO(50000 元以上)
public sealed class CeoApprover(string name) : ExpenseApprover
{
protected override Task<ApprovalResult?> TryApproveAsync(ExpenseClaim claim, CancellationToken ct)
=> claim.Amount > 50000
? Task.FromResult<ApprovalResult?>(new(ApprovalDecision.Approved, name, "CEO 审批通过"))
: Task.FromResult<ApprovalResult?>(null);
}
3.4 构建审批链
csharp
namespace Approvals.Expenses;
public static class ApprovalChainFactory
{
/// <summary>按公司当前规则组装审批链(规则变更只需改这里)</summary>
public static ExpenseApprover BuildDefaultChain()
{
var supervisor = new SupervisorApprover("张主管");
var manager = new DepartmentManagerApprover("李经理");
var finance = new FinanceDirectorApprover("王总监");
var ceo = new CeoApprover("刘总");
// 链式连接:主管 → 经理 → 总监 → CEO
supervisor.SetNext(manager).SetNext(finance).SetNext(ceo);
return supervisor; // 返回链头
}
}
3.5 使用:请求丢进链头即可
csharp
// Program.cs(.NET 10 顶层语句)
using Approvals.Expenses;
var chain = ApprovalChainFactory.BuildDefaultChain();
var claims = new[]
{
new ExpenseClaim("CL-001", "张三", 300m, "打车费"),
new ExpenseClaim("CL-002", "李四", 3000m, "出差机票"),
new ExpenseClaim("CL-003", "王五", 30000m, "设备采购"),
new ExpenseClaim("CL-004", "赵六", 100000m, "服务器集群")
};
foreach (var claim in claims)
{
var result = await chain.HandleAsync(claim);
Console.WriteLine($"{claim.ClaimId}(¥{claim.Amount})→ {result.ApprovedBy}:{result.Remark}");
}
// 输出示例:
// CL-001(¥300)→ 张主管:主管审批通过
// CL-002(¥3000)→ 李经理:部门经理审批通过
// CL-003(¥30000)→ 王总监:财务总监审批通过
// CL-004(¥100000)→ 刘总:CEO 审批通过
3.6 拓展:规则变了怎么办?
公司调整规则:"5000 以上且超过 20000 的需要法务会签"。
csharp
// 新增一个节点,插进链中即可,业务代码零改动
public sealed class LegalApprover(string name) : ExpenseApprover
{
protected override Task<ApprovalResult?> TryApproveAsync(ExpenseClaim claim, CancellationToken ct)
=> claim.Amount is > 20000
? Task.FromResult<ApprovalResult?>(new(ApprovalDecision.Approved, name, "法务会签通过"))
: Task.FromResult<ApprovalResult?>(null);
}
// 组装时插入:主管 → 经理 → 财务 → 法务 → CEO
四、应用要点与注意事项
- 责任链的两种形态 :
- 终止型:找到一个能处理的节点就结束(本示例------审批流);
- 全链型:所有节点都处理一遍(中间件管道------每个请求都过日志、鉴权、限流)。ASP.NET Core 中间件就是全链型责任链的经典实现。
- 请求与处理解耦:发送方只需要知道链头,不知道也不关心谁最终处理。新增节点、调整顺序都不影响发送方。
- 注意链的健壮性:如果整条链都没有节点能处理,必须有兜底逻辑(示例中返回了"无审批人可处理")。链断了(某节点没设 Next)也要能优雅结束。
- 结合模板方法使用 :
HandleAsync的"先试自己、不行再传"骨架就是模板方法模式,子类只实现TryApproveAsync。两个模式天然搭配。 - 与 if/else 的取舍:如果规则简单固定(两三个分支),直接写条件判断反而更直观;当节点数量、顺序、规则经常变化,或节点本身有复杂逻辑时,责任链才真正值得。
五、小结
责任链模式把"谁能处理"的判断从业务代码里解放出来,变成一条可动态组装、可插拔的链。审批流、异常处理、日志管道、请求过滤都是它的主场。它和模板方法、装饰器、中间件思想一脉相承------理解"链",就理解了现代框架中一大半的管道设计。