依赖第三方验证的Value Object构建难题求解
解决方案:值对象结合第三方验证库的合理实践
针对值对象(Value Object)依赖第三方验证库的问题,核心要满足两个要求:值对象保持纯不可变数据类的特性(不持有协作对象)、杜绝外部创建无效值对象,以下是几种可行方案:
方案1:静态工厂方法封装验证逻辑(推荐用于简单场景)
将第三方验证逻辑完全封装在值对象的静态工厂方法中,同时将构造函数设为私有,禁止外部直接实例化。值对象对外仅暴露接受原始类型参数的创建接口,内部自行调用验证库完成校验。
示例代码(PHP):
final class Phone { private string $normalizedNumber; private string $countryCode; // 私有构造函数,确保只能通过工厂方法创建 private function __construct(string $normalizedNumber, string $countryCode) { $this->normalizedNumber = $normalizedNumber; $this->countryCode = $countryCode; } /** * 对外暴露的创建方法,接受原始号码和国家码 * @throws InvalidArgumentException 号码无效时抛出 */ public static function create(string $rawNumber, string $countryCode): self { // 内部初始化第三方验证库 $phoneUtil = \libphonenumber\PhoneNumberUtil::getInstance(); try { $parsedNumber = $phoneUtil->parse($rawNumber, $countryCode); if (!$phoneUtil->isValidNumber($parsedNumber)) { throw new InvalidArgumentException("Invalid phone number for country code: $countryCode"); } // 标准化号码格式,确保值对象内部存储统一格式 $normalized = $phoneUtil->format($parsedNumber, \libphonenumber\PhoneNumberFormat::E164); return new self($normalized, $countryCode); } catch (\libphonenumber\NumberParseException $e) { throw new InvalidArgumentException("Failed to parse phone number: {$e->getMessage()}", 0, $e); } } // 值对象核心方法:获取值、相等性判断 public function getNumber(): string { return $this->normalizedNumber; } public function equals(Phone $other): bool { return $this->normalizedNumber === $other->normalizedNumber && $this->countryCode === $other->countryCode; } }
优点:
- 完全符合值对象定义:仅依赖原始类型,无外部协作对象依赖
- 构造函数私有,从根源上杜绝无效实例
- 业务代码无需关心验证细节,调用简单
缺点:
- 验证库与值对象耦合,测试时替换验证逻辑需要修改值对象代码(可通过方案2优化)
方案2:独立工厂类+依赖注入(推荐用于需要测试或复杂场景)
将验证逻辑剥离到独立的工厂类中,通过依赖注入传入第三方验证器,同时保持值对象的构造函数私有,仅允许工厂类创建实例。
示例代码:
// 独立工厂类,负责验证和创建Phone对象 class PhoneFactory { private \libphonenumber\PhoneNumberUtil $phoneUtil; // 通过构造函数注入验证器,方便测试时替换 public function __construct(\libphonenumber\PhoneNumberUtil $phoneUtil) { $this->phoneUtil = $phoneUtil; } /** * 创建合法的Phone对象 * @throws InvalidArgumentException 号码无效时抛出 */ public function create(string $rawNumber, string $countryCode): Phone { try { $parsedNumber = $this->phoneUtil->parse($rawNumber, $countryCode); if (!$this->phoneUtil->isValidNumber($parsedNumber)) { throw new InvalidArgumentException("Invalid phone number"); } $normalized = $this->phoneUtil->format($parsedNumber, \libphonenumber\PhoneNumberFormat::E164); // 调用Phone内部的合法创建入口 return Phone::fromValidatedNumber($normalized, $countryCode); } catch (\libphonenumber\NumberParseException $e) { throw new InvalidArgumentException("Failed to parse phone number", 0, $e); } } } final class Phone { private string $normalizedNumber; private string $countryCode; private function __construct(string $normalizedNumber, string $countryCode) { $this->normalizedNumber = $normalizedNumber; $this->countryCode = $countryCode; } // 仅对工厂开放的创建入口(可通过命名空间或访问控制限制) public static function fromValidatedNumber(string $normalizedNumber, string $countryCode): self { // 二次校验确保传入的是合法标准化值 if (empty($normalizedNumber) || empty($countryCode)) { throw new InvalidArgumentException("Normalized number and country code cannot be empty"); } return new self($normalizedNumber, $countryCode); } // 其他值对象方法... public function getNumber(): string { return $this->normalizedNumber; } }
优点:
- 解耦值对象与验证库,测试时可轻松替换验证器实现
- 依然保证值对象的纯数据特性,外部无法直接创建无效实例
- 符合依赖注入原则,便于容器管理
缺点:
- 多引入一个工厂类,增加了少量代码复杂度
方案3:基于验证接口的灵活创建(用于多验证策略场景)
定义统一的验证接口,值对象的静态工厂接受接口实例完成验证,但值对象本身不持有接口实例,仅在创建时临时使用。
示例代码:
// 定义验证接口 interface PhoneValidator { /** * 验证并标准化号码,失败抛出异常 * @return string 标准化后的号码 */ public function validate(string $rawNumber, string $countryCode): string; } // 基于Google libphone的实现 class GooglePhoneValidator implements PhoneValidator { private \libphonenumber\PhoneNumberUtil $phoneUtil; public function __construct() { $this->phoneUtil = \libphonenumber\PhoneNumberUtil::getInstance(); } public function validate(string $rawNumber, string $countryCode): string { try { $parsedNumber = $this->phoneUtil->parse($rawNumber, $countryCode); if (!$this->phoneUtil->isValidNumber($parsedNumber)) { throw new InvalidArgumentException("Invalid phone number"); } return $this->phoneUtil->format($parsedNumber, \libphonenumber\PhoneNumberFormat::E164); } catch (\libphonenumber\NumberParseException $e) { throw new InvalidArgumentException("Failed to parse phone number", 0, $e); } } } final class Phone { private string $normalizedNumber; private string $countryCode; private function __construct(string $normalizedNumber, string $countryCode) { $this->normalizedNumber = $normalizedNumber; $this->countryCode = $countryCode; } /** * 接受验证接口,灵活切换验证策略 */ public static function create(string $rawNumber, string $countryCode, PhoneValidator $validator): self { $normalizedNumber = $validator->validate($rawNumber, $countryCode); return new self($normalizedNumber, $countryCode); } // 其他值对象方法... }
优点:
- 支持多种验证策略,可根据场景动态切换
- 值对象依然保持纯数据特性,无长期协作对象依赖
缺点:
- 业务代码需要传递验证器实例,增加了调用复杂度(可通过DI容器绑定默认实例优化)
内容的提问来源于stack exchange,提问作者jockos
相关产品推荐
相关产品推荐

