对我的域规范应用的值对象执行规则


Do rules on value objects applied by my domain specification

一个简单的问题是,我在域中有一个email值对象,而在我的域中,电子邮件可以是(null,有效的电子邮件),那么在这种情况下,如果我想将电子邮件对象传递给我的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的新手,所以我不确定:)

您的第一个选项是错误的,因为类有两种可能的状态,而其中一种是无效的,因为null无法完全填充Email接口。想象一下,使用该类的一个实例,即使电子邮件不是可选的,您也必须检查电子邮件是否有效这使得类型安全有点毫无意义:

class Consumer{
    // email is not optional at all
    function __construct(Email $email){
        if(!$email->isActuallyAnEmail()) // WTH?
            throw new Exception;
    }
}

您尝试使用的是NullObject模式。但是,如果使用NullObjects来代替仅行为接口,则效果会更好;相反,在您的情况下,您是围绕标量数据建模的,标量数据是电子邮件字符串:NullObject模式可以使用,只要它看起来工作正常,并且对调用方代码做得非常好。如果接口应该在某个地方返回标量数据(在您的情况下是电子邮件字符串),那么实现所述接口的NullObject只能弥补该数据。所以这实际上不是一个NullObject,而是我所说的Fake/Example/Placeholder对象。大致:

// 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附加到"联系人信息"对象)。

如果你想了解更多关于null处理的信息,也可以查看Option模式/Maybe monad,它理解起来有点复杂;你可以看一下,但不是严格要求的。

这里有两个ValueObject:电子邮件和联系人信息,两者都可以有不同的不变量。

如果您的电子邮件VO具有null值,则该VO应无效(电子邮件(null)应引发异常)。然而,即使没有设置电子邮件,您的ContactInfo也是有效的。

在我看来,对于构造函数,您应该只传递对象有效所需的值,如果不需要电子邮件,则不要将其传递到那里。

当然,默认参数会让事情变得更容易,但它往往隐藏了设置值的真正含义。

在这个例子中很难看到,但看看这个:


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类型直接传递给构造函数,但它真的说明了我们做了什么吗?它不太冗长。

然而,在你的例子中,这很好。