对我的域规范应用的值对象执行规则
Do rules on value objects applied by my domain specification
一个简单的问题假设我在我的域中有一个 email
值对象,在我的域中电子邮件可以是(空的,有效的电子邮件),所以在这种情况下,如果我想传递一个将对象通过电子邮件发送到我的 contact_info
值对象哪个选项更有效? //从ddd的角度来看
选项 1:
class email{
public function construct($email)
{
if($email !== null)
{
// Assert that the email is valid
}
$this->email = $email
}
}
class contact_info{
public function __construct(Email $email){
}
}
选项 2:
class email{
public function construct($email)
{
// Assert that the email is valid
$this->email = $email
}
}
class contact_info{
public function __construct(Email $email = null){
if($email !== null){
$this->email = $email;
}
}
}
显然选项 1 更好,并且它的抽象具有更小的误差范围。但我是 ddd 的新手,所以我不确定 :)
这里有两个 ValueObjects:email 和 contact info,两者可以有不同的不变量。
如果您的电子邮件 VO 具有空值,则它应该是无效的(Email(null) 应该引发异常)。然而,即使未设置电子邮件,您的联系信息也是有效的。
在我看来,对于构造函数,您应该只传递对象有效所需的值,如果不需要电子邮件,则不要传递它。
当然,默认参数使事情变得更容易,但它通常隐藏了设置值的真正含义。
在这个例子中很难看出,但是看看这个:
class Person:
def __init__(self, id, type=USER):
self._id = id
self._type = type
VS
class Person:
def __init__(self, id):
self._id = id
self._type = USER
def give_admin_permissions(self):
self._type = ADMIN
我们可以将 ADMIN 类型直接传递给构造函数,但它真的说明了我们所做的吗?不会太冗长。
然而在你的例子中没问题。
您的第一个选项是错误的,因为 class 有两种可能的状态,而 一个是无效的 ,因为 null
无法满足 Email
界面。想象一下使用那个 class 的实例,你将不得不检查电子邮件是否有效, 即使电子邮件不是可选的。 这使得类型安全有点毫无意义:
class Consumer{
// email is not optional at all
function __construct(Email $email){
if(!$email->isActuallyAnEmail()) // WTH?
throw new Exception;
}
}
您尝试使用的是 NullObject 模式。但是,如果使用 NullObjects 代替纯行为接口,NullObjects 会工作得更好;相反,在你的例子中,你是围绕标量数据建模的,它是电子邮件字符串:NullObject 模式可以使用,只要 它似乎可以正常工作并且做事非常好 来电者代码。如果该接口应该在某处 return 标量数据(在您的例子中是电子邮件字符串),则实现该接口的 NullObject 只能组成该数据。所以这实际上不是 NullObject,而是我所说的 Fake/Example/Placeholder objects。大致:
// behavioral-only, a perfectly fine NullObject
class NullLogger implements Logger{
function log($message){ return true; }
}
// this sucks. you are just converting null to errors
// or moving errors to another place
// this NullObject implementation is invalid
// as it doesn't actually fullfill the Email interface
class NullEmail implements Email{
function __toString(){ throw new Exception; }
}
// not behavioral-only, an ExampleObject in this case
class ExampleEmail implements Email{
function __toString(){ return "example@example.org"; }
}
显然,这具有完全不同的含义。它不再是可选值。数据在那里,但它是编造的;它是一个示例值、默认值或只是一个占位符。
因此,选项 2 是正确的(除非您可以将 example@example.org
附加到您的 "contact info" 对象)。
如果您想了解更多关于 null
处理的信息,请同时查看 Option pattern / Maybe monad ,它理解起来有点复杂;你可以看一下,但不是严格要求的。
一个简单的问题假设我在我的域中有一个 email
值对象,在我的域中电子邮件可以是(空的,有效的电子邮件),所以在这种情况下,如果我想传递一个将对象通过电子邮件发送到我的 contact_info
值对象哪个选项更有效? //从ddd的角度来看
选项 1:
class email{
public function construct($email)
{
if($email !== null)
{
// Assert that the email is valid
}
$this->email = $email
}
}
class contact_info{
public function __construct(Email $email){
}
}
选项 2:
class email{
public function construct($email)
{
// Assert that the email is valid
$this->email = $email
}
}
class contact_info{
public function __construct(Email $email = null){
if($email !== null){
$this->email = $email;
}
}
}
显然选项 1 更好,并且它的抽象具有更小的误差范围。但我是 ddd 的新手,所以我不确定 :)
这里有两个 ValueObjects:email 和 contact info,两者可以有不同的不变量。
如果您的电子邮件 VO 具有空值,则它应该是无效的(Email(null) 应该引发异常)。然而,即使未设置电子邮件,您的联系信息也是有效的。
在我看来,对于构造函数,您应该只传递对象有效所需的值,如果不需要电子邮件,则不要传递它。
当然,默认参数使事情变得更容易,但它通常隐藏了设置值的真正含义。
在这个例子中很难看出,但是看看这个:
class Person:
def __init__(self, id, type=USER):
self._id = id
self._type = type
VS
class Person:
def __init__(self, id):
self._id = id
self._type = USER
def give_admin_permissions(self):
self._type = ADMIN
我们可以将 ADMIN 类型直接传递给构造函数,但它真的说明了我们所做的吗?不会太冗长。
然而在你的例子中没问题。
您的第一个选项是错误的,因为 class 有两种可能的状态,而 一个是无效的 ,因为 null
无法满足 Email
界面。想象一下使用那个 class 的实例,你将不得不检查电子邮件是否有效, 即使电子邮件不是可选的。 这使得类型安全有点毫无意义:
class Consumer{
// email is not optional at all
function __construct(Email $email){
if(!$email->isActuallyAnEmail()) // WTH?
throw new Exception;
}
}
您尝试使用的是 NullObject 模式。但是,如果使用 NullObjects 代替纯行为接口,NullObjects 会工作得更好;相反,在你的例子中,你是围绕标量数据建模的,它是电子邮件字符串:NullObject 模式可以使用,只要 它似乎可以正常工作并且做事非常好 来电者代码。如果该接口应该在某处 return 标量数据(在您的例子中是电子邮件字符串),则实现该接口的 NullObject 只能组成该数据。所以这实际上不是 NullObject,而是我所说的 Fake/Example/Placeholder objects。大致:
// behavioral-only, a perfectly fine NullObject
class NullLogger implements Logger{
function log($message){ return true; }
}
// this sucks. you are just converting null to errors
// or moving errors to another place
// this NullObject implementation is invalid
// as it doesn't actually fullfill the Email interface
class NullEmail implements Email{
function __toString(){ throw new Exception; }
}
// not behavioral-only, an ExampleObject in this case
class ExampleEmail implements Email{
function __toString(){ return "example@example.org"; }
}
显然,这具有完全不同的含义。它不再是可选值。数据在那里,但它是编造的;它是一个示例值、默认值或只是一个占位符。
因此,选项 2 是正确的(除非您可以将 example@example.org
附加到您的 "contact info" 对象)。
如果您想了解更多关于 null
处理的信息,请同时查看 Option pattern / Maybe monad ,它理解起来有点复杂;你可以看一下,但不是严格要求的。