本文目录导读:

是的,PHP 中规约模式非常适合用于验证业务规则。
规约模式(Specification Pattern)的核心思想是将业务规则封装成独立的、可组合的对象,它不仅仅是数据验证(如字段格式、非空),而是专门用于处理复杂的业务逻辑判断。
下面详细解释规约模式如何应用于业务规则验证,并提供一个 PHP 示例。
规约模式 vs. 传统验证方法
| 特性 | 传统验证(如 if-else、Laravel FormRequest) |
规约模式 |
|---|---|---|
| 关注点 | 数据格式、字段完整性(输入验证) | 业务逻辑、领域规则(如:用户是否可以借款、订单是否可取消) |
| 复用性 | 低,通常写在控制器或请求类中,难跨场景复用 | 高,每个规则都是独立对象,可在不同地方组合使用 |
| 可读性 | 复杂业务逻辑下,if-else 嵌套难读 |
逻辑清晰,语义化强($isEligible = $canBorrow->and($isInGoodStanding)) |
| 变化性 | 规则变化需修改原有代码 | 规则变化只需修改对应规约对象或组合方式 |
| 组合性 | 难组合,逻辑硬编码 | 天然支持逻辑运算(and, or, not) |
一句话总结: 传统验证检查输入“对不对”,规约模式检查领域“行不行”。
核心概念
- 规约接口:定义核心方法
isSatisfiedBy($candidate),返回bool。 - 具体规约:实现接口,封装单个业务规则。
- 组合规约:通过
AndSpecification、OrSpecification、NotSpecification等,将多个简单规约组合成复杂的业务规则。
PHP 示例
假设我们有一个贷款审批系统,需要验证一个用户是否有资格申请贷款,规则如下:
- 用户年龄必须在 18-65 岁之间。
- 用户月收入必须大于 5000。
- 用户信用评分必须大于 700。
步骤 1:定义规约接口
<?php
interface Specification
{
public function isSatisfiedBy($candidate): bool;
}
步骤 2:实现具体规约
<?php
class User
{
public function __construct(
public readonly int $age,
public readonly float $monthlyIncome,
public readonly int $creditScore
) {}
}
class AgeSpecification implements Specification
{
public function isSatisfiedBy($user): bool
{
return $user->age >= 18 && $user->age <= 65;
}
}
class IncomeSpecification implements Specification
{
public function __construct(private readonly float $minIncome = 5000) {}
public function isSatisfiedBy($user): bool
{
return $user->monthlyIncome >= $this->minIncome;
}
}
class CreditScoreSpecification implements Specification
{
public function __construct(private readonly int $minScore = 700) {}
public function isSatisfiedBy($user): bool
{
return $user->creditScore >= $this->minScore;
}
}
步骤 3:实现组合规约(让规则可以组合)
<?php
class AndSpecification implements Specification
{
/** @param Specification[] $specifications */
public function __construct(private readonly array $specifications) {}
public function isSatisfiedBy($candidate): bool
{
foreach ($this->specifications as $spec) {
if (!$spec->isSatisfiedBy($candidate)) {
return false;
}
}
return true;
}
}
class OrSpecification implements Specification
{
/** @param Specification[] $specifications */
public function __construct(private readonly array $specifications) {}
public function isSatisfiedBy($candidate): bool
{
foreach ($this->specifications as $spec) {
if ($spec->isSatisfiedBy($candidate)) {
return true;
}
}
return false;
}
}
class NotSpecification implements Specification
{
public function __construct(private readonly Specification $specification) {}
public function isSatisfiedBy($candidate): bool
{
return !$this->specification->isSatisfiedBy($candidate);
}
}
步骤 4:使用规约进行业务判断
<?php
// 创建用户对象(待判断的数据)
$user = new User(age: 28, monthlyIncome: 8000, creditScore: 750);
// 创建具体规约
$ageSpec = new AgeSpecification();
$incomeSpec = new IncomeSpecification(minIncome: 5000);
$creditSpec = new CreditScoreSpecification(minScore: 700);
// 组合规则:必须同时满足(AND)
$loanApprovalRule = new AndSpecification([$ageSpec, $incomeSpec, $creditSpec]);
// 执行验证
if ($loanApprovalRule->isSatisfiedBy($user)) {
echo "用户符合贷款条件。"; // 输出这条
} else {
echo "用户不符合贷款条件。";
}
// ---------- 高级用法:灵活组合 ----------
// 规则改变:年龄或信用评分满足其一即可(OR),但收入必须达标(AND)
$flexibleRule = new AndSpecification([
$incomeSpec, // 收入必须达标
new OrSpecification([$ageSpec, $creditSpec]) // 年龄或信用满足其一
]);
if ($flexibleRule->isSatisfiedBy($user)) {
echo "用户符合灵活贷款条件。";
} else {
echo "用户不符合灵活贷款条件。";
}
// 排除指定情况:年龄18-65,收入大于5000,且信用**不**低于700
$excludeRule = new AndSpecification([
$ageSpec,
$incomeSpec,
new NotSpecification(new CreditScoreSpecification(minScore: 700)) // 注意:这里语义可能是“信用分不高于700” -> 实际业务需调整
]);
// 更恰当的写法:信用分不低于700 = 信用分 >= 700,上面的写法是“不满足信用分>=700”即“信用分<700”
// 所以直接使用 CreditScoreSpecification 即可,这里仅为演示 NotSpecification
$correctExcludeRule = new AndSpecification([
$ageSpec,
$incomeSpec,
$creditSpec // 直接包含即可,表示信用达标
]);
优势总结
- 业务逻辑清晰:
$rule = new AndSpecification([$ageSpec, $incomeSpec])比if ($age >= 18 && $age <= 65 && $income > 5000)更语义化。 - 高复用性:
AgeSpecification可以在贷款、租房、会员注册等多个场景复用。 - 易于扩展:新增一个“居住城市”规则,只需新增
CitySpecification,然后在组合中加入即可,无需修改原有规约。 - 避免“规则爆炸”:不用为每种业务组合编写一个独立方法,而是通过组合规约动态生成。
- 适合复杂场景:当业务规则经常变化(如运营活动条件频繁调整),或规则数量很多时,规约模式的维护成本远低于硬编码。
适用场景
- 领域驱动设计 (DDD):作为领域层的一部分,封装聚合根或实体的业务规则。
- 复杂过滤/查询:例如在开发工作中,根据用户角色、部门、状态、创建时间等组合条件筛选待办任务。
- 工作流/审批系统:判断某个环节是否可以自动通过。
- 权限/策略校验:判断用户是否有执行特定操作的权限。
不适合的场景
- 简单的表单输入验证(如非空、邮箱格式、字符串长度),这种情况使用 PHP 的
filter_var()、validator组件(如 Laravel 的Validator)或symfony/validator更直接。 - 只有 1-2 个固定规则,且未来几乎不变,此时硬编码更简单。
是的,规约模式非常适合验证业务规则。 它让你的业务逻辑代码更健壮、更灵活、更易于维护,尤其在面对复杂的、多变的、需要组合的业务规则时,规约模式是一个极其强大的工具,如果你正在开发一个业务逻辑较复杂的 PHP 项目(如 ERP、CRM、金融系统),引入规约模式会显著提升代码质量。