【问题标题】:Is it okay to store a domain entity's mutable properties as a value object?可以将域实体的可变属性存储为值对象吗?
【发布时间】:2012-03-17 06:32:01
【问题描述】:

我希望能够更改和传递我的 UserEntity 的某些部分,并且某些部分应该保持不变。

例如,我从不想更改我的 UserEntity 的 ID,但电子邮件或密码之类的内容可能会经常更改,并且也可以被 UserEntity 之外的其他对象使用。

其中一个实例是在创建 UserEntity 时。由于没有 id 就不能存在 UserEntity,我的控制器可以创建一个 UserData 对象来标准化 UserEntity 属性。映射器在 db 中创建实体后,将创建一个新的 UserEntity 并在构造函数中传入 id 和 UserData 对象。

当 UserEntity 需要电子邮件或密码等信息时,它只需查看其 UserData。

似乎更便携,但这是否矫枉过正?有没有更好的解决方案?

注意

  • 我认为这可能很好的原因是:可变字段的值需要标准化......有时这些字段需要在实体本身之外传递。例如,在创建实体之前。通过创建一个可以传递的值对象,我们提供了一个标准化的点来从任何地方分配这些值,以及可以在实体外部传递的东西。

  • “标准化”是指我的信息需要统一,无论它存在于何处。例如,email 需要始终为 n 长度且格式有效,name 始终需要为 n 长度等。我的目标是我希望能够设置那些“规则”在一个地方......并且由于 UserEntity 的这些属性(可变属性)存在于实体本身之外,有时,它们可能会独立存在于它们自己的值对象中。

【问题讨论】:

  • 对我来说听起来不错 :) 大概,您的 ID 字段是私有的,在您的 UserEntity 中只有 get() 访问它...
  • 这就是想法 :) 但是可变属性的任何值设置器都将在值对象中。
  • 在实体对象上仅包含电子邮件、密码和所有其他字段有什么问题?拥有单独的数据对象有什么好处?
  • 主要是那些字段需要标准化...有时这些字段需要在实体本身之外传递。例如,在创建实体之前。通过创建一个可以传递的值对象,我们提供了一个标准化的点来从任何地方分配这些值,以及可以在实体外部传递的东西。
  • 如果您将实体数据与实体本身分开传递,那么您做错了。在 OOP 中,数据不应该与功能分开 - 是什么让一个类成为它的样子。例如,您能否更具体地说明在创建对象之前如何处理数据?

标签: php architecture separation-of-concerns


【解决方案1】:

我认为没有“一种真正的方法”可以做到这一点(无论你读到什么)......如果它在你的模型中有意义,那么这对我来说听起来不错。当您说“其中许多字段需要标准化”时,我不确定您的意思是什么,以及为什么不能作为 UserEntity 的一部分来完成,但无论如何。也就是说,您很可能无需完全独立的对象类就可以完成您想要做的事情。

评论/批评:

您的建议并不真正符合严格的“对象”模型,即 UserData 只是由真正属于 UserEntity 属性的事物组成,并且与这些属性没有其他潜在关系。

我不太确定为什么您需要一个单独的对象在实体外部传递...如果您需要数据,为什么不能只传递 UserEntity 并从那里访问它?在将数据传递给 UserEntity 构造函数之前,您需要对数据执行什么操作,而这无法通过在 stdClass 的实例中收集数据然后在 UserEntity 中处理它来轻松完成?


如果是我,我会做类似以下的事情(例如,创建一个新用户):

<?
// assume an appropriately defined UserEntity class...

// I'm using stdClass just to keep the parameters together to pass all at once
// I'm assuming some basic user data passed from the browser
$user_data = (object) array(
    'email' => $_REQUEST['email'],
    'name' => $_REQUEST['name'],
    'password' => $_REQUEST['password'],
    'confirm_password' => $_REQUEST['confirm_password']
);

/*
validateData is static so it can be called before you create the new user
It takes the $user_data object to validate and, if necessary, modify fields.
It also takes a $create flag which indicates whether the data should be
checked to make sure all of the necessary fields are there to create the user
with.  This allows you to call it on update with the $create flag unset and it
will pass validation even if it's missing otherwise required fields.
It returns $result, which indicates pass or failure, and the potentially modified
$user_data object
*/
$create = TRUE;
list($result, $user_data) = UserEntity::validateData($user_data, $create);

// equivalence allows you to pass back descriptive error messages
if ($result === TRUE) {
    // create the user in the database, get back $user_id...
    $user = new UserEntity($user_id, $user_data);
}
else {
    // return error to user
}

// access user data either individually, or if you want just make a getter
// for the entire group of data, so you can use it just like you would a
// separate UserData object
send_double_opt_in($user->getUserData());
?>

编辑以解决提供的更多信息:

您说这些属性存在于 UserEntity 之外,并且它们可能独立存在...您的意思是这些属性可以被收集、使用和丢弃,甚至不打算用于 UserEntity 对象?如果是这种情况,那么单独的对象将完全适合该数据。如果不是,如果数据始终从属于现有或未来的 UserEntity,那么这些属性将永远不会“独立存在”......让我们称之为“全局数据”的观点。当您将整个系统视为一个整体,而不仅仅是时时刻刻的代码时,数据很可能“属于” UserEntity 类。

至于静态方法,我认为没有特别的理由避免它们(显然),但每个人都有自己的理由。许多其他架构会稍微复杂一些,但这里有一些选项:

  1. 验证构造函数中的数据。问题是,如果它不验证,您将不得不删除数据库条目。丑陋。
  2. 将数据库交互与数据验证一起移动到构造函数中。这可能会违反您首选的对象模型,您只需在对象创建后检查它的状态(即设置公共属性$this-&gt;status = 'error'; 或类似的东西来告诉您发生了一些不好的事情,您将不得不句柄)。
  3. 创建一个独立的函数来验证数据。丑陋,因为这是一个专门与 UserEntity 和/或其数据相关的函数。
  4. 或者,只需按照您的建议创建一个单独的 UserData 对象并完成它。与第 2 步非常相似,您必须拥有某种 $status 属性来指示验证失败。

【讨论】:

  • 感谢您的回答 - 澄清一下,“标准化”是指我的信息需要统一,无论它存在于何处。例如,电子邮件必须始终为n 长度且格式有效,名称始终需要为n 长度等。我的目标(您实际解决的问题)是我希望能够设置这些“规则”集中在一个地方......并且由于 UserEntity 的这些属性(可变的)存在于实体本身之外,有时,它们可能会独立存在。
  • 话虽如此,您只需要一个地方来验证数据。但是,我倾向于回避任何静态函数……如果我要走这条路,这实际上将是整个项目的第一个。有了我上面的评论,并且没有静态函数,你会提供任何修改吗?
  • 我已更新我的答案以反映新的信息和偏好
  • johnnietheblack,如果您需要在电子邮件或姓名上强制执行某些属性 - 您应该为它们创建验证器或单独的类:例如,电子邮件。
  • 但是(大概)与UserEntity 的数据库实现相关的特定长度要求呢?这些在UserEntity 之外毫无意义,并且可以想象,对于系统的不同部分可能会有所不同。这就是为什么我认为将这些规则合并到UserEntity 类中最有意义的原因。也就是说,如果使用一个单独的全局适用的验证器,比如电子邮件,那么可以在构造函数中管理长度要求,而不必担心遇到错误(假设所需的长度总是足够长)。
【解决方案2】:

对于一组可以独立于任何域对象使用的验证器来说,这似乎是一个很好的案例——也许这是我的函数式编程方面的出现,但我认为创建一个一堆函数,比如isValidEmail()

我知道您对一堆静态函数感到不舒服;您是否会考虑使用带有一堆方法的静态 Validator 类来保持整洁?

无论哪种方式,您都可以在 UserEntity 对象之外使用此验证(我假设您正在谈论其他域对象、控制器中的输入验证等情况?)。但是您也可以在“UserEntity”对象中使用它——就我自己而言,我还没有确定我是否更喜欢在设置属性时进行清理和验证,或者将其保存到持久存储中。我目前更倾向于第一个,通过 setter 函数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-29
    • 2014-09-24
    • 2014-02-04
    • 1970-01-01
    • 2011-04-17
    • 2023-01-25
    • 2015-12-23
    • 2018-10-05
    相关资源
    最近更新 更多