规约模式
规约模式
简介
规约(Specification)将业务规则封装为可组合的对象。规约模式将筛选条件、验证规则和业务逻辑抽象为独立的规约类,支持链式组合(AND、OR、NOT),使业务规则可复用、可测试。
规约模式最早由 Eric Evans 和 Martin Fowler 分别提出。Eric Evans 在 DDD 中将其用于封装领域规则,Martin Fowler 则将其用于查询条件组合。两者的核心思想一致:将一个业务规则或查询条件封装为一个独立的对象,使得规则可以被复用、组合和传递。
在实际开发中,我们经常遇到"满足某些条件的用户"、"符合某些规则的订单"等需求。如果直接在代码中写 if/else 或 LINQ 表达式,这些规则会分散在代码各处,难以复用和测试。规约模式将每条规则封装为一个独立的类,并通过 AND、OR、NOT 操作符组合出复杂的业务逻辑。
特点
结构分析
UML 类图
+--------------------+
| ISpecification<T> |
+--------------------+
| +ToExpression() |
| +IsSatisfiedBy() |
+--------------------+
^
|
+--------------------+
| Specification<T> | <-- 抽象基类
+--------------------+
| +And(other) |
| +Or(other) |
| +Not() |
+--------------------+
^
|
+-------------+-------------+
| | |
+------------+ +------------+ +----------+
| ActiveUser | | AdultUser | | AndSpec |
| Spec | | Spec | | OrSpec |
+------------+ +------------+ | NotSpec |
+----------+组合逻辑图
ActiveUser AND AdultUser AND AdminUser AND RecentUser(30)
+----------+ AND +----------+ AND +----------+ AND +----------+
| Active | ------->| Adult | ------->| Admin | ------->| Recent |
| User | | User | | User | | User(30) |
+----------+ +----------+ +----------+ +----------+
ActiveUser OR (AdminUser AND RecentUser(30))
+----------+ OR +---------------------------+
| Active | ------->| Admin AND Recent(30) |
| User | | +-------+ AND +--------+ |
+----------+ | | Admin | | Recent | |
| +-------+ +--------+ |
+---------------------------+实现
基础规约框架
// 规约接口
public interface ISpecification<T>
{
Expression<Func<T, bool>> ToExpression();
bool IsSatisfiedBy(T candidate);
}
// 基础规约
public abstract class Specification<T> : ISpecification<T>
{
public abstract Expression<Func<T, bool>> ToExpression();
public bool IsSatisfiedBy(T candidate) => ToExpression().Compile()(candidate);
public Specification<T> And(ISpecification<T> other) => new AndSpecification<T>(this, other);
public Specification<T> Or(ISpecification<T> other) => new OrSpecification<T>(this, other);
public Specification<T> Not() => new NotSpecification<T>(this);
}
// 组合规约
public class AndSpecification<T> : Specification<T>
{
private readonly ISpecification<T> _left;
private readonly ISpecification<T> _right;
public AndSpecification(ISpecification<T> left, ISpecification<T> right) { _left = left; _right = right; }
public override Expression<Func<T, bool>> ToExpression()
{
var leftExpr = _left.ToExpression();
var rightExpr = _right.ToExpression();
var param = Expression.Parameter(typeof(T));
var body = Expression.AndAlso(
Expression.Invoke(leftExpr, param),
Expression.Invoke(rightExpr, param));
return Expression.Lambda<Func<T, bool>>(body, param);
}
}
public class OrSpecification<T> : Specification<T>
{
private readonly ISpecification<T> _left, _right;
public OrSpecification(ISpecification<T> left, ISpecification<T> right) { _left = left; _right = right; }
public override Expression<Func<T, bool>> ToExpression()
{
var leftExpr = _left.ToExpression();
var rightExpr = _right.ToExpression();
var param = Expression.Parameter(typeof(T));
var body = Expression.OrElse(Expression.Invoke(leftExpr, param), Expression.Invoke(rightExpr, param));
return Expression.Lambda<Func<T, bool>>(body, param);
}
}
public class NotSpecification<T> : Specification<T>
{
private readonly ISpecification<T> _spec;
public NotSpecification(ISpecification<T> spec) => _spec = spec;
public override Expression<Func<T, bool>> ToExpression()
{
var expr = _spec.ToExpression();
var param = Expression.Parameter(typeof(T));
return Expression.Lambda<Func<T, bool>>(Expression.Not(Expression.Invoke(expr, param)), param);
}
}业务规约定义
// 领域实体
public class User
{
public Guid Id { get; set; }
public string Name { get; set; } = "";
public string Email { get; set; } = "";
public int Age { get; set; }
public string Role { get; set; } = "User";
public bool IsActive { get; set; }
public DateTime RegisteredAt { get; set; }
}
// 具体规约
public class ActiveUserSpec : Specification<User>
{
public override Expression<Func<User, bool>> ToExpression() => u => u.IsActive;
}
public class AdultUserSpec : Specification<User>
{
public override Expression<Func<User, bool>> ToExpression() => u => u.Age >= 18;
}
public class AdminUserSpec : Specification<User>
{
public override Expression<Func<User, bool>> ToExpression() => u => u.Role == "Admin";
}
public class RecentUserSpec : Specification<User>
{
private readonly int _days;
public RecentUserSpec(int days) => _days = days;
public override Expression<Func<User, bool>> ToExpression()
=> u => u.RegisteredAt >= DateTime.UtcNow.AddDays(-_days);
}
// 使用 — 组合规约
var activeAdultAdmin = new ActiveUserSpec()
.And(new AdultUserSpec())
.And(new AdminUserSpec());
// 内存筛选
var users = new List<User> { /* ... */ };
var matching = users.Where(u => activeAdultAdmin.IsSatisfiedBy(u)).ToList();
// 数据库查询 — 转为 EF Core 表达式
var spec = new ActiveUserSpec().And(new RecentUserSpec(30));
var recentActiveUsers = await dbContext.Users.Where(spec.ToExpression()).ToListAsync();规约与验证
public class Order
{
public decimal Amount { get; set; }
public string Status { get; set; } = "";
public List<string> Items { get; set; } = new();
}
public class ValidOrderSpec : Specification<Order>
{
public override Expression<Func<Order, bool>> ToExpression() =>
o => o.Amount > 0 && o.Items.Count > 0 && !string.IsNullOrEmpty(o.Status);
}
public class LargeOrderSpec : Specification<Order>
{
public override Expression<Func<Order, bool>> ToExpression() => o => o.Amount > 10000;
}
// 规约验证器
public class SpecificationValidator
{
public static bool Validate<T>(T entity, ISpecification<T> spec, out string error)
{
var satisfied = spec.IsSatisfiedBy(entity);
error = satisfied ? "" : $"不满足规约: {spec.GetType().Name}";
return satisfied;
}
}实战:电商促销规则
在电商系统中,促销规则往往非常复杂且经常变化。使用规约模式可以将每条促销规则封装为独立的规约类。
public class Product
{
public string Name { get; set; } = "";
public decimal Price { get; set; }
public string Category { get; set; } = "";
public int Stock { get; set; }
public bool IsNewArrival { get; set; }
public DateTime CreatedAt { get; set; }
}
// 促销规约
public class PriceRangeSpec : Specification<Product>
{
private readonly decimal _min;
private readonly decimal _max;
public PriceRangeSpec(decimal min, decimal max) { _min = min; _max = max; }
public override Expression<Func<Product, bool>> ToExpression()
=> p => p.Price >= _min && p.Price <= _max;
}
public class InStockSpec : Specification<Product>
{
public override Expression<Func<Product, bool>> ToExpression() => p => p.Stock > 0;
}
public class CategorySpec : Specification<Product>
{
private readonly string _category;
public CategorySpec(string category) => _category = category;
public override Expression<Func<Product, bool>> ToExpression() => p => p.Category == _category;
}
public class NewArrivalSpec : Specification<Product>
{
private readonly int _days;
public NewArrivalSpec(int days = 30) => _days = days;
public override Expression<Func<Product, bool>> ToExpression()
=> p => p.CreatedAt >= DateTime.UtcNow.AddDays(-_days);
}
// 组合促销规则:电子产品 + 有库存 + 价格在 500-5000 之间
var electronicsPromo = new CategorySpec("Electronics")
.And(new InStockSpec())
.And(new PriceRangeSpec(500, 5000));
// 新品推荐:所有新品 + 有库存
var newArrivals = new NewArrivalSpec(7)
.And(new InStockSpec());
// 使用 — 既可以用于内存筛选,也可以用于数据库查询
var products = dbContext.Products.Where(electronicsPromo.ToExpression()).ToList();实战:带参数的通用规约
// 通用属性比较规约
public class PropertyEqualSpec<T, TProp> : Specification<T>
{
private readonly Expression<Func<T, TProp>> _property;
private readonly TProp _value;
public PropertyEqualSpec(Expression<Func<T, TProp>> property, TProp value)
{
_property = property;
_value = value;
}
public override Expression<Func<T, bool>> ToExpression()
{
var parameter = _property.Parameters[0];
var equalExpr = Expression.Equal(_property.Body, Expression.Constant(_value, typeof(TProp)));
return Expression.Lambda<Func<T, bool>>(equalExpr, parameter);
}
}
// 使用
var nameEqualsZhang = new PropertyEqualSpec<User, string>(u => u.Name, "张三");
var roleEqualsAdmin = new PropertyEqualSpec<User, string>(u => u.Role, "Admin");
var combined = nameEqualsZhang.And(roleEqualsAdmin);规约与仓储模式集成
规约模式与仓储(Repository)模式结合使用时,可以将查询逻辑完全从仓储中解耦,仓储只负责数据访问,规约负责定义查询条件。
// 规约感知的仓储接口
public interface ISpecificationRepository<T> where T : class
{
Task<T?>FirstOrDefaultAsync(ISpecification<T> spec, CancellationToken ct = default);
Task<List<T>> ListAsync(ISpecification<T> spec, CancellationToken ct = default);
Task<int> CountAsync(ISpecification<T> spec, CancellationToken ct = default);
Task<bool> AnyAsync(ISpecification<T> spec, CancellationToken ct = default);
}
// 仓储实现
public class SpecificationRepository<T> : ISpecificationRepository<T> where T : class
{
private readonly DbContext _context;
public SpecificationRepository(DbContext context)
{
_context = context;
}
public async Task<T?> FirstOrDefaultAsync(ISpecification<T> spec, CancellationToken ct = default)
{
return await _context.Set<T>()
.Where(spec.ToExpression())
.FirstOrDefaultAsync(ct);
}
public async Task<List<T>> ListAsync(ISpecification<T> spec, CancellationToken ct = default)
{
return await _context.Set<T>()
.Where(spec.ToExpression())
.ToListAsync(ct);
}
public async Task<int> CountAsync(ISpecification<T> spec, CancellationToken ct = default)
{
return await _context.Set<T>()
.Where(spec.ToExpression())
.CountAsync(ct);
}
public async Task<bool> AnyAsync(ISpecification<T> spec, CancellationToken ct = default)
{
return await _context.Set<T>()
.Where(spec.ToExpression())
.AnyAsync(ct);
}
}
// 使用示例
var repo = new SpecificationRepository<User>(dbContext);
// 查找活跃的成年管理员
var spec = new ActiveUserSpec().And(new AdultUserSpec()).And(new AdminUserSpec());
var admins = await repo.ListAsync(spec);
// 检查是否存在满足条件的用户
var hasRecentActive = await repo.AnyAsync(new ActiveUserSpec().And(new RecentUserSpec(7)));
var recentCount = await repo.CountAsync(new RecentUserSpec(30));规约单元测试
每个规约类都可以独立测试,这是规约模式的核心优势之一。测试覆盖正常条件、边界条件和否定情况。
[TestFixture]
public class UserSpecificationTests
{
private static User CreateTestUser(
bool isActive = true, int age = 25, string role = "User",
DateTime? registeredAt = null)
{
return new User
{
Id = Guid.NewGuid(),
Name = "测试用户",
IsActive = isActive,
Age = age,
Role = role,
RegisteredAt = registeredAt ?? DateTime.UtcNow
};
}
[Test]
public void ActiveUserSpec_ActiveUser_ReturnsTrue()
{
var user = CreateTestUser(isActive: true);
var spec = new ActiveUserSpec();
Assert.IsTrue(spec.IsSatisfiedBy(user));
}
[Test]
public void ActiveUserSpec_InactiveUser_ReturnsFalse()
{
var user = CreateTestUser(isActive: false);
var spec = new ActiveUserSpec();
Assert.IsFalse(spec.IsSatisfiedBy(user));
}
[Test]
public void AdultUserSpec_Exactly18_ReturnsTrue()
{
var user = CreateTestUser(age: 18);
var spec = new AdultUserSpec();
Assert.IsTrue(spec.IsSatisfiedBy(user));
}
[Test]
public void AdultUserSpec_Age17_ReturnsFalse()
{
var user = CreateTestUser(age: 17);
var spec = new AdultUserSpec();
Assert.IsFalse(spec.IsSatisfiedBy(user));
}
[Test]
public void CombinedSpec_AndLogic_Works()
{
var user = CreateTestUser(isActive: true, age: 25, role: "Admin");
var spec = new ActiveUserSpec().And(new AdultUserSpec()).And(new AdminUserSpec());
Assert.IsTrue(spec.IsSatisfiedBy(user));
}
[Test]
public void CombinedSpec_AndLogic_OneFalse_Fails()
{
// 活跃但未成年
var user = CreateTestUser(isActive: true, age: 16);
var spec = new ActiveUserSpec().And(new AdultUserSpec());
Assert.IsFalse(spec.IsSatisfiedBy(user));
}
[Test]
public void NotSpec_InvertsResult()
{
var inactiveUser = CreateTestUser(isActive: false);
var notActive = new ActiveUserSpec().Not();
Assert.IsTrue(notActive.IsSatisfiedBy(inactiveUser));
}
[Test]
public void OrSpec_EitherTrue_Passes()
{
var user = CreateTestUser(isActive: false, role: "Admin");
var spec = new ActiveUserSpec().Or(new AdminUserSpec());
Assert.IsTrue(spec.IsSatisfiedBy(user));
}
[TestCase(0, false)]
[TestCase(-1, false)]
[TestCase(1, true)]
public void InStockSpec_VariousQuantities(int stock, bool expected)
{
var product = new Product { Stock = stock };
var spec = new InStockSpec();
Assert.AreEqual(expected, spec.IsSatisfiedBy(product));
}
}规约的 EF Core 兼容性
在使用规约模式进行数据库查询时,需要特别注意 Expression 能否被 EF Core 正确翻译为 SQL。
// 兼容:能被翻译为 SQL
public class CompatibleSpec : Specification<User>
{
public override Expression<Func<User, bool>> ToExpression()
=> u => u.IsActive && u.Age >= 18;
// SQL: WHERE IsActive = 1 AND Age >= 18
}
// 不兼容:无法被翻译
public class IncompatibleSpec : Specification<User>
{
public override Expression<Func<User, bool>> ToExpression()
=> u => u.Name.Contains("test") && u.RegisteredAt.ToLocalTime() > DateTime.Now;
// ToLocalTime() 无法翻译为 SQL,会抛出异常或触发客户端评估
}
// 安全做法:使用 UTC 时间和可翻译的方法
public class SafeTimeSpec : Specification<User>
{
private readonly int _days;
public SafeTimeSpec(int days) => _days = days;
public override Expression<Func<User, bool>> ToExpression()
{
var cutoff = DateTime.UtcNow.AddDays(-_days);
return u => u.RegisteredAt >= cutoff;
}
// 在表达式外部计算 cutoff,而不是在 Expression 内部调用 AddDays
}
// 常见不可翻译的操作:
// - DateTime.ToLocalTime() / ToLongDateString()
// - string.Format() / interpolated strings
// - 自定义方法调用
// - BitConverter.ToString() 等 BCL 方法
// 验证表达式是否可翻译的辅助方法
public static class SpecificationValidator
{
public static bool CanTranslateToSql<T>(
ISpecification<T> spec,
DbContext context) where T : class
{
try
{
var _ = context.Set<T>()
.Where(spec.ToExpression())
.ToQueryString();
return true;
}
catch (InvalidOperationException)
{
return false;
}
}
}性能考量
规约模式在表达式组合时会引入额外的 Expression 树构建开销,需要关注以下性能要点:
// 1. 表达式缓存:避免重复编译
public static class SpecificationCache
{
private static readonly ConcurrentDictionary<string, object> _cache = new();
public static Func<T, bool> GetCompiledFunc<T>(ISpecification<T> spec)
{
var key = $"{typeof(T).Name}_{spec.GetType().Name}";
return (Func<T, bool>)_cache.GetOrAdd(key, _ => spec.ToExpression().Compile());
}
}
// 2. 参数化规约:避免闭包捕获导致表达式膨胀
public class EfficientDateRangeSpec<T> : Specification<T>
{
private readonly DateTime _start;
private readonly DateTime _end;
private readonly Expression<Func<T, DateTime>> _dateSelector;
public EfficientDateRangeSpec(
Expression<Func<T, DateTime>> dateSelector,
DateTime start, DateTime end)
{
_dateSelector = dateSelector;
_start = start;
_end = end;
}
public override Expression<Func<T, bool>> ToExpression()
{
var param = _dateSelector.Parameters[0];
var body = Expression.AndAlso(
Expression.GreaterThanOrEqual(_dateSelector.Body, Expression.Constant(_start)),
Expression.LessThanOrEqual(_dateSelector.Body, Expression.Constant(_end)));
return Expression.Lambda<Func<T, bool>>(body, param);
}
}
// 3. 规约与分页:组合规约时结合分页查询
public class PaginatedSpecificationQuery<T> where T : class
{
private readonly DbContext _context;
public PaginatedSpecificationQuery(DbContext context)
{
_context = context;
}
public async Task<(List<T> Items, int TotalCount)> ExecuteAsync(
ISpecification<T> spec, int page, int pageSize, CancellationToken ct = default)
{
var query = _context.Set<T>().Where(spec.ToExpression());
var totalCount = await query.CountAsync(ct);
var items = await query
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync(ct);
return (items, totalCount);
}
}
// 4. 规约与排序
public class OrderedSpecificationQuery<T> where T : class
{
public static IQueryable<T> ApplyOrderBy(
IQueryable<T> query, ISpecification<T> spec,
Expression<Func<T, object>> orderBy, bool descending = false)
{
query = query.Where(spec.ToExpression());
return descending ? query.OrderByDescending(orderBy) : query.OrderBy(orderBy);
}
}规约序列化与动态规则引擎
在某些场景下,需要将规约持久化到数据库,允许非开发人员配置业务规则。
// 规约定义数据模型
public class SpecificationDefinition
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string TargetType { get; set; } = ""; // 实体类型名称
public string Operator { get; set; } = ""; // Equal, GreaterThan, Contains 等
public string Property { get; set; } = ""; // 属性名
public string Value { get; set; } = ""; // 比较值
public string Combinator { get; set; } = ""; // AND, OR
public int ParentId { get; set; } // 组合关系
}
// 动态规约构建器
public class DynamicSpecificationBuilder
{
public static ISpecification<T> Build<T>(List<SpecificationDefinition> definitions)
{
if (definitions.Count == 0)
return new AlwaysTrueSpec<T>();
var specs = definitions.Select(d => BuildSingle<T>(d)).ToList();
var combinator = definitions.FirstOrDefault()?.Combinator ?? "AND";
return combinator == "OR"
? specs.Aggregate((a, b) => (Specification<T>)a.Or(b))
: specs.Aggregate((a, b) => (Specification<T>)a.And(b));
}
private static ISpecification<T> BuildSingle<T>(SpecificationDefinition def)
{
var param = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(param, def.Property);
var constant = Expression.Constant(
Convert.ChangeType(def.Value, property.Type));
BinaryExpression comparison = def.Operator switch
{
"Equal" => Expression.Equal(property, constant),
"NotEqual" => Expression.NotEqual(property, constant),
"GreaterThan" => Expression.GreaterThan(property, constant),
"LessThan" => Expression.LessThan(property, constant),
"GreaterThanOrEqual" => Expression.GreaterThanOrEqual(property, constant),
"LessThanOrEqual" => Expression.LessThanOrEqual(property, constant),
_ => Expression.Equal(property, constant)
};
var lambda = Expression.Lambda<Func<T, bool>>(comparison, param);
return new ExpressionSpec<T>(lambda);
}
}
// 辅助规约
public class AlwaysTrueSpec<T> : Specification<T>
{
public override Expression<Func<T, bool>> ToExpression() => x => true;
}
public class ExpressionSpec<T> : Specification<T>
{
private readonly Expression<Func<T, bool>> _expr;
public ExpressionSpec(Expression<Func<T, bool>> expr) => _expr = expr;
public override Expression<Func<T, bool>> ToExpression() => _expr;
}
// 使用:从数据库加载规则并构建规约
var rules = new List<SpecificationDefinition>
{
new() { Property = "Age", Operator = "GreaterThanOrEqual", Value = "18" },
new() { Property = "IsActive", Operator = "Equal", Value = "True", Combinator = "AND" }
};
var dynamicSpec = DynamicSpecificationBuilder.Build<User>(rules);
var qualifiedUsers = await dbContext.Users.Where(dynamicSpec.ToExpression()).ToListAsync();与 LINQ 表达式的对比
规约模式 直接 LINQ
+------------------+ +------------------+
| 规则可复用 | | 每次重写 |
| 可组合 AND/OR | | 需手动拼接 |
| 可用于验证 | | 仅用于查询 |
| 类数量多 | | 简洁直接 |
| 可传递给仓储 | | 紧耦合查询上下文 |
+------------------+ +------------------+
建议:
- 简单条件 -> 直接用 LINQ
- 复杂规则需要复用 -> 用规约模式
- 规则需要组合 -> 用规约模式
- 规则需要验证 -> 用规约模式最佳实践
- 规约类命名以 Spec 结尾:如
ActiveUserSpec、PriceRangeSpec,方便识别。 - 规约应该是无状态的:规约对象不包含业务状态,只包含规则定义。
- 避免过度组合:超过 5 层 AND/OR 组合会使表达式难以调试。
- 考虑 EF Core 兼容性:确保组合后的 Expression 能被 EF Core 翻译为 SQL。
- 规约与仓储配合:仓储接受规约作为查询条件,实现查询逻辑与领域逻辑的分离。
优点
缺点
总结
规约模式将业务规则封装为可组合的 Expression 对象,同时支持内存筛选和数据库查询。基础操作有 And、Or、Not 三种组合方式。规约可用于查询条件、验证规则、业务约束等场景。建议在规则复用性强、组合查询多的业务领域使用规约模式,简单查询直接用 LINQ 表达式即可。
规约模式的本质价值在于:将业务规则从分散的 if/else 和 LINQ 表达式中提取出来,封装为可复用、可组合、可测试的独立对象。当业务规则复杂且经常变化时,规约模式能显著提高代码的可维护性。
关键知识点
- 模式不是目标,降低耦合和控制变化才是目标。
- 先找变化点、稳定点和协作边界,再决定是否引入模式。
- 同一个模式在不同规模下的收益和代价差异很大。
项目落地视角
- 优先画出参与对象、依赖方向和调用链,再落到代码。
- 把模式放到一个真实场景里,比如支付、规则引擎、工作流或插件扩展。
- 配合单元测试或契约测试,保证重构后的行为没有漂移。
常见误区
- 为了看起来"高级"而套模式。
- 把简单问题拆成过多抽象层,导致阅读和排障都变难。
- 只会背 UML,不会解释为什么这里需要这个模式。
进阶路线
- 继续关注模式之间的组合用法,而不是孤立记忆。
- 从业务建模、演进策略和团队协作角度看模式的适用性。
- 把模式结论沉淀为项目模板、基类或约束文档。
适用场景
- 当你准备把《规约模式》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在业务规则频繁变化、分支增多或对象协作复杂时引入。
- 当你希望提高扩展性,但又不想把系统拆得过度抽象时,这类主题很有参考价值。
落地建议
- 先识别变化点,再决定是否引入模式,而不是反过来套模板。
- 优先为模式的边界、依赖和调用路径画出简单结构图。
- 把模式落到一个明确场景,例如支付、规则计算、插件扩展或工作流。
排错清单
- 检查抽象层是否过多,导致调用路径和责任不清晰。
- 确认引入模式后是否真的减少了条件分支和重复代码。
- 警惕"为了模式而模式",尤其是在简单业务里。
复盘问题
- 如果把《规约模式》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《规约模式》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《规约模式》最大的收益和代价分别是什么?
