【问题标题】:Should all value objects be immutable所有值对象都应该是不可变的吗
【发布时间】:2014-04-25 10:29:46
【问题描述】:

我有 1 个 Employee 实体,它拥有一个 ContactInformation 的值对象:

Employee.php

class Employee {
    private $contactInformation;

    public function __construct(ContactInformation $contactInformation) {
        $this->contactInformation = $contactInformation;
    }

    public function getContactInformation() {
        return $this->contactInformation;
    }
}

ContactInformation.php

class ContactInformation {
    private $email;        // Required
    private $cellPhone;    // Required
    private $phone;        // Optional
    private $address;      // Optional

    public function __construct($email, $cellPhone) {
        $this->email = $email;
        $this->cellphone = $cellphone;
    }

    /** Here goes a bunch of setter getter for those properties **/
}

如果员工的手机换了,岂不是更好吗

$employee->getContactInformation()->setPhone($newPhone)

而不是强制诸如

之类的不变性

$employee->setContactInformation(new ContactInformation(/** 复制粘贴 这里**/));

根据 Eric Evans 的书,我了解到,当您更改值对象的一部分时,它与之前的值对象完全不同。

对于像这个 ContactInformation 这样稍微复杂一点的对象,我无法得出相同的结论,它应该是一个实体吗?

需要建议,谢谢!

【问题讨论】:

  • 我会选择第一种方法,很少有第二种更好的情况。
  • @RoyalBg 我也是,但我就是无法摆脱这种关于如何将 ContactInformation 视为实体的恶心感觉
  • 为什么不使用 ContactInformation 扩展 Employee 并允许 $employee->setPhone()?
  • @VincentNikkelen 这听起来有点奇怪.. 几乎就像说 Employee 对我来说是一个 ContactInformation
  • @SamuelAdam 对我来说,员工最好不要依赖联系信息,反之亦然。您不能单独拥有联系信息,因为它应该绑定到某个员工

标签: php oop domain-driven-design


【解决方案1】:

对我来说,Employee 依赖 ContactInformation 意味着您希望 ContactInformation 已经设置了电话等,然后传递给员工,即:

$contanctInfo = new ContactInformation();
$contanctInfo->setPhone(820);
$employee1 = new Employee($contanctInfo);
echo $employee1->getContanctInformation()->getPhone(); // 820

但是,这意味着在实例化每个 Employee 之前,您需要创建与 Employee 对象一样多的 ContactInformation 对象,因为:

$contanctInfo = new ContactInformation();
$contanctInfo->setPhone(820);
$employee1 = new Employee($contanctInfo);
$employee2 = new Employee($contanctInfo);
$employee1->getContactInformation()->setPhone(123);
$employee2->getContactInformation()->setPhone(666);

echo $employee1->getContactInformation()->getPhone();
echo $employee2->getContactInformation()->getPhone();

变成:

666
666

因为你改变的是同一个对象。

在您的情况下,您同时实例化ContactInformation,当您实例化Employee 时使依赖关系有点无用:

$employee1 = new Employee(new ContactInformation());
$employee2 = new Employee(new ContactInformation());

$employee1->getContactInformation()->setPhone(123);
$employee2->getContactInformation()->setPhone(666);

echo $employee1->getContactInformation()->getPhone();
echo $employee2->getContactInformation()->getPhone();

结果成:

123
666

因此,如果您不想在将依赖对象注入另一个对象之前对其进行更改,则根本不需要依赖项。更重要的是,在现实世界中,ContanctInformation 绑定到 Employee,而不是相反。

我会这样:

class Employee {

    private $_contactInformation;

    public function __construct() {
        $this->_contactInformation = new ContactInformation($this);
    }

    /**
     * @return ContactInformation
     */
    public function getContactInformation() {
        return $this->_contactInformation;
    }
}

class ContactInformation {

    private $_employee;

    private $email;        // Required
    private $cellPhone;    // Required
    private $phone;        // Optional
    private $address;      // Optional

    public function __construct(Employee $employee) {
        $this->_employee = $employee;
    }

    public function setPhone($phone) {
        $this->phone = $phone;
    }

    public function getPhone() {
        return $this->phone;
    }
}

$employee1 = new Employee();
$employee2 = new Employee();

$employee1->getContactInformation()->setPhone(123);
$employee2->getContanctInformation()->setPhone(666);

echo $employee1->getContactInformation()->getPhone();
echo $employee2->getContactInformation()->getPhone();

另一方面,如果 Employee 有一些标识符,那么ContanctInfo 会根据 ID 检索信息,你可以按照你想要的方式进行注入。

【讨论】:

  • 谢谢你的阐述,多一双眼睛就好了
【解决方案2】:

值对象应该是不可变的,但ContactInformation 不应该是值对象。

假设两个人共用一个房子,因此拥有相同的地址和电话号码(如果您假设他们没有手机或电子邮件,这可以简化问题)。

在这种情况下,他们将拥有相同的ContactInformation 对象,但联系信息仍明显属于某个人或另一个人。如果他们中的一个人搬出去,或者买了一部手机,那么他们的联系信息就必须改变。

正如您在评论中提到的,最好将 ContactInformation 视为可变实体,而不是值对象。

【讨论】:

    【解决方案3】:

    有趣的讨论。我所做的是让 getContactInformation 返回一个克隆。

    class Employee {
    
    public function getContactInformation() {
        return clone $this->contactInformation;
    }
    ...
    $contactInformation = $employee->getContactInformation();
    $contactInformation->setPhone('');
    $employee->setContactInformation($contactInformation);
    

    通过这样做,实际存储在 Employee 内部的值对象是不可变的。您总是提取一个副本,因为它不再与员工相关联,因此不再是一个价值对象(人们可能会与大量挥手争论),因此可以在不惹恼 DDD 警察的情况下进行修改。

    为我工作,让我使用一个需要设置器的表单系统。

    【讨论】:

      猜你喜欢
      • 2012-02-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-04
      • 2015-09-03
      • 1970-01-01
      • 2010-09-25
      • 2013-02-28
      相关资源
      最近更新 更多