规格模式(Specification):把业务规则变成乐高积木
说实话,我第一次接触规格模式是在一个电商后台项目里。当时有个需求——查询「可参与满减活动的商品」,条件多得让人头大:商品要上架、库存要大于0、价格要超过门槛、不能是黑名单商品、用户等级要达标……if 嵌套了七八层,看得我直摇头。
后来我重构了那段代码,用的就是规格模式。说白了,就是把每个判断条件封装成一个独立的「规格」,然后用 AND、OR、NOT 这些逻辑运算符把它们组合起来。你想想看,这不就是业务逻辑的乐高积木吗?
什么是规格模式?
规格模式的核心思想很简单:把业务规则抽象成接口,每个规则是一个独立的对象,然后通过组合器把它们拼装起来。这样做的最大好处是——业务变化时,你只需要增删规格,不用动核心逻辑。
我个人的习惯是,只要发现业务条件超过 3 个,而且未来可能变化,就优先考虑规格模式。它特别适合做查询条件动态组装、权限校验、工作流规则判断这些场景。
核心角色
| 角色 | 说明 |
|---|---|
| 规格接口(ISpecification) | 定义判断方法,以及 AND、OR、NOT 组合操作 |
| 具体规格(ConcreteSpecification) | 实现单个业务规则,比如「价格大于100」 |
| 复合规格(CompositeSpecification) | 负责 AND、OR、NOT 的逻辑组合 |
| 客户端 | 组装规格并执行判断或过滤 |
先看接口定义
嗯,这里要注意:接口里除了 isSatisfiedBy 方法,还要提供组合方法。我个人喜欢用抽象类来实现默认的组合逻辑,这样具体规格就不用重复写了。
// Java 版本
public interface ISpecification<T> {
boolean isSatisfiedBy(T candidate);
ISpecification<T> and(ISpecification<T> other);
ISpecification<T> or(ISpecification<T> other);
ISpecification<T> not();
}
public abstract class CompositeSpecification<T> implements ISpecification<T> {
@Override
public ISpecification<T> and(ISpecification<T> other) {
return new AndSpecification<>(this, other);
}
@Override
public ISpecification<T> or(ISpecification<T> other) {
return new OrSpecification<>(this, other);
}
@Override
public ISpecification<T> not() {
return new NotSpecification<>(this);
}
}
// C++ 版本
template <typename T>
class ISpecification {
public:
virtual ~ISpecification() = default;
virtual bool isSatisfiedBy(const T& candidate) const = 0;
virtual std::shared_ptr<ISpecification<T>> and_(std::shared_ptr<ISpecification<T>> other) const;
virtual std::shared_ptr<ISpecification<T>> or_(std::shared_ptr<ISpecification<T>> other) const;
virtual std::shared_ptr<ISpecification<T>> not_() const;
};
复合规格的实现
复合规格说白了就是逻辑运算的封装。我曾经见过有人直接在业务代码里写 if (a && b || !c),后来条件一变,改得满头大汗。用复合规格,每个逻辑运算都是一个类,清晰又好测。
// Java 版本
public class AndSpecification<T> extends CompositeSpecification<T> {
private ISpecification<T> left;
private ISpecification<T> right;
public AndSpecification(ISpecification<T> left, ISpecification<T> right) {
this.left = left;
this.right = right;
}
@Override
public boolean isSatisfiedBy(T candidate) {
return left.isSatisfiedBy(candidate) && right.isSatisfiedBy(candidate);
}
}
public class OrSpecification<T> extends CompositeSpecification<T> {
// 类似 AndSpecification,用 || 连接
}
public class NotSpecification<T> extends CompositeSpecification<T> {
private ISpecification<T> spec;
public NotSpecification(ISpecification<T> spec) {
this.spec = spec;
}
@Override
public boolean isSatisfiedBy(T candidate) {
return !spec.isSatisfiedBy(candidate);
}
}
具体规格:把业务规则写清楚
每个具体规格只做一件事。比如「价格大于100」就是一个规格,「库存大于0」是另一个规格。这样每个规格都可以单独测试,出了问题也容易定位。
// Java 版本
public class PriceGreaterThanSpec extends CompositeSpecification<Product> {
private double threshold;
public PriceGreaterThanSpec(double threshold) {
this.threshold = threshold;
}
@Override
public boolean isSatisfiedBy(Product product) {
return product.getPrice() > threshold;
}
}
public class InStockSpec extends CompositeSpecification<Product> {
@Override
public boolean isSatisfiedBy(Product product) {
return product.getStock() > 0;
}
}
public class CategorySpec extends CompositeSpecification<Product> {
private String category;
public CategorySpec(String category) {
this.category = category;
}
@Override
public boolean isSatisfiedBy(Product product) {
return category.equals(product.getCategory());
}
}
动态组装查询条件
这才是规格模式最爽的地方。你想想看,前端传过来一堆筛选条件,后端不用写一长串 if-else,直接组装规格就行。
// Java 版本:动态组装
public List<Product> queryProducts(ProductQuery query) {
ISpecification<Product> spec = new AlwaysTrueSpec<>();
if (query.getMinPrice() != null) {
spec = spec.and(new PriceGreaterThanSpec(query.getMinPrice()));
}
if (query.getCategory() != null) {
spec = spec.and(new CategorySpec(query.getCategory()));
}
if (query.getInStockOnly()) {
spec = spec.and(new InStockSpec());
}
// 还可以排除某些品类
if (query.getExcludeCategory() != null) {
spec = spec.and(new NotSpecification<>(new CategorySpec(query.getExcludeCategory())));
}
return productRepository.findAll().stream()
.filter(spec::isSatisfiedBy)
.collect(Collectors.toList());
}
核心要点:规格模式把「判断逻辑」和「组合逻辑」彻底分离。每个规格只关心自己那一个条件,组合方式由客户端决定。这样业务规则的变化,就变成了规格的增删改,而不是大段代码的重写。
SVG 结构图:规格模式的核心逻辑
避坑指南
我曾经踩过的一个坑:一开始我把所有规格都设计成单例,想着反正逻辑一样。结果有个规格需要根据用户等级动态调整阈值,单例就出问题了——多个请求互相覆盖参数。后来我改成每次组装时 new 新的规格实例,问题就解决了。
我的建议:规格模式虽然好用,但别滥用。如果只有一两个条件,直接写 if 反而更简洁。我一般会在条件超过 3 个,或者业务规则经常变化时,才引入规格模式。另外,配合工厂模式使用效果更佳——把规格的创建集中管理,客户端只需要传参数就行。
实际项目中的应用
我记得在一个权限系统中,我们用规格模式实现了动态的权限校验。每个权限规则是一个规格,比如「用户属于管理员组」、「操作时间在 9:00-18:00」、「IP 在白名单内」。然后通过 AND/OR 组合出复杂的权限策略。后来业务方要加一个新规则,我们只需要新增一个规格类,然后在配置中心里改一下组合方式,完全不用动核心代码。
说白了,规格模式就是让业务规则变得可组合、可扩展、可测试。你想想看,如果每个规则都是独立的小单元,改一个不会影响另一个,测试也能单独测,多省心。
公众号:蓝海资料掘金营,微信deep3321