引入 Parameter Object:可扩展的重构模式
Source: Dev.to
请提供需要翻译的正文内容,我才能为您进行简体中文翻译。
介绍
有一种我经常使用的重构模式很少得到应有的关注。它看起来并不惊人,却能防止代码库在自身重量下慢慢崩溃。
问题:冗长的参数列表
你一定见过类似下面的函数签名:
public function createInvoice(
int $customerId,
string $currency,
float $netAmount,
float $taxRate,
string $country,
bool $isReverseCharge,
?string $discountCode,
DateTime $issueDate
): Invoice
起初它还能工作,但随着业务需求的增长,调用点会变得脆弱。冗长的参数列表不仅是审美上的问题;在实际中它会导致:
- 难以阅读的函数调用
- 代码审查时的高认知负荷
- 参数顺序错误的常见 bug
- 变更会波及数十个调用点
- 基于恐惧的重构(“别动它”综合症)
最重要的是,它隐藏了参数之间的隐式关系。当多个参数 总是一起传递、代表单一领域概念、并且 因相同业务原因而一起变化 时,签名未能清晰地表达领域语言。
引入参数对象
该模式不仅仅是为了减少参数的数量;更在于将相关的数据组织成有意义的对象。
示例分组
// Tax‑related data
float $netAmount,
float $taxRate,
bool $isReverseCharge,
string $country
// Pricing context
string $currency,
?string $discountCode
小而专注的对象
class TaxContext
{
public function __construct(
public float $netAmount,
public float $taxRate,
public bool $isReverseCharge,
public string $country
) {}
}
class PricingContext
{
public function __construct(
public string $currency,
public ?string $discountCode
) {}
}
之前 → 之后
之前
public function createInvoice(
int $customerId,
string $currency,
float $netAmount,
float $taxRate,
string $country,
bool $isReverseCharge,
?string $discountCode,
DateTime $issueDate
): Invoice
之后
public function createInvoice(
int $customerId,
PricingContext $pricing,
TaxContext $tax,
DateTime $issueDate
): Invoice
行为 不会 改变;仅是接口变得更清晰。
Benefits
1. 可读的调用
Before
$service->createInvoice(
$customerId,
'EUR',
1000,
0.21,
'DE',
false,
null,
new DateTime()
);
After
$pricing = new PricingContext('EUR', null);
$tax = new TaxContext(1000, 0.21, false, 'DE');
$service->createInvoice(
$customerId,
$pricing,
$tax,
new DateTime()
);
无需解码参数位置。
2. 本地化的未来更改
当出现新的税务相关需求时,只需扩展 TaxContext:
class TaxContext
{
public function __construct(
public float $netAmount,
public float $taxRate,
public bool $isReverseCharge,
public string $country,
public ?string $vatId = null
) {}
}
方法签名保持不变。
3. 靠近数据的验证与行为
class TaxContext
{
public function isTaxApplicable(): bool
{
return !$this->isReverseCharge && $this->taxRate > 0;
}
}
将逻辑与数据封装在一起,使得该模式随时间产生复利效应。
何时使用
- 三个或以上参数 已经形成了有意义的领域概念。
- 相同的一组参数在多个调用点出现。
- 单一业务变更一次性影响多个参数。
确切的数量并不重要,关键是代码通过接口难以表达意图的信号。
陷阱与技巧
- 避免“愚蠢的数据包”。 如果对象从未获得行为,你就错失了部分价值。
- 逐步重构。 从一个方法开始;这不是一次性的大改动。
- 确保对象能够回答领域问题。 永远不获得行为的参数对象通常表明重构不完整。
结论
引入参数对象并不显眼,但它悄然提升了:
- 可读性
- 可维护性
- 变更弹性
- 开发者信心
在成熟的代码库中,这些是最重要的重构。
接下来是什么?
在后续的文章中,我将探讨其他“安静”的重构模式,例如:
- 重构时间耦合
- 提取领域特定查询对象
- 将验证逻辑迁移到领域概念中
小改动,长期影响。