我有一个关于松散耦合OOP设计的问题。假设我们有一个简单的值对象,如电子邮件
final class Email
{
private $_email;
public function __construct($email)
{
self::isValid($email);
$this->_email = $email;
}
public function getEmail()
{
return $this->_email;
}
public static function isValid($email)
{
// some validation logic goes here
return true;
}
}
在我想真正实现isValid方法之前,一切都很简单明了。我这里有两个选项:
1) 实现我自己的验证逻辑,它可能非常丑陋,比如这样:
public static function isValid($email)
{
$v = preg_match(
'/^[-a-z0-9!#$%&'*+/=?^_`{|}~]+(?:.[-a-z0-9!#$%&'*+/=?^_`{|}~]+)*@(?:[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?.)*(?:aero|arpa|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|pro|[a-z][a-z])$/',
$email
);
return $v > 0;
}
2) 使用一些内置的框架验证器
public static function isValid($email)
{
$validator = new Zend_Validate_Email(); // tight-coupling detected!
return $validator->isValid($email);
}
我真的不想走第一条路,因为我不想重新发明轮子,也不想重复代码,所以我坚持走第二条路。
如果我按照第二种方法,我就会遇到问题——我的类依赖于另一个框架类。
我的实际问题是在简单的情况下,直接在实体/值对象中使用低级基础结构类而不使用依赖注入是否可以接受?
如果我"正确地"实现这个例子,仅仅为了实现松散耦合,代码就会变得更加复杂。我必须创建一个EmailFactory,它将为我的Value Object(电子邮件)类提供一个预配置的EmailValidator实例,该实例将用于isValid函数…
只要验证函数不需要外部资源,我就会直接重用依赖项并避免它的注入。
域对象封装域规则,并且应该包含确保这些规则的所有必要信息。它的设计更多地围绕着高内聚性而非低耦合性(聚合的概念本身是减少相互依赖性的一种手段)。
示例中的Email
类应该是电子邮件地址验证的单点失败。如果所有域对象都通过该类验证电子邮件,那么重用现有的框架(甚至第三方)功能就是一个实现细节,并且不太容易出现松耦合问题。如果验证规则发生更改,您无论如何都必须使用Zend_Validate_Email
或一些自定义代码重新实现该函数。
将验证逻辑注入实体或值对象会带来很多问题:
属于域对象的- 逻辑将被移动到另一个类
- 一般不建议向实体注入服务
- 只为
EmailValidator
类引入接口违反了重用抽象原则 - 在
EMail
上调用静态isValid
方法是不可能的
只有在验证逻辑需要外部资源的罕见情况下,我倾向于使用带有注入服务的工厂,该工厂在创建域对象时进行验证,但只有在重新思考域模型的设计之后。