我正在尽我所能创建一个Web应用程序来熟悉DDD和ValueObjects。到目前为止一切正常,我只是问自己我是否正确实现了我的值对象。这是我当前的状态(从非必要部分剥离的代码):
class User {
private $id;
/**
* @var Credentials
*/
public $credentials;
public function getId() { return $this->id; }
}
class Credentials {
private $username;
private $password;
public function __construct($username, $password) { // no need to detail }
public function changePassword($newPassword) {
return new self($this->username, $newPassword);
}
}
因此,每当我想更新用户密码时,我都必须$user->credentials = $user->credentials->changePassword("newPassword");
这是对的吗?我应该为User类的$credentials
属性创建一个getter和setter吗?我应该将changePassword()
方法放在User类中吗?
非常感谢任何帮助!
答案 0 :(得分:3)
$user->credentials = $user->credentials->changePassword("newPassword");
这是正确的想法。
在此实现中,$ username和$ password是Credentials状态的一部分。你希望那个状态是不可变的;在构造函数完成后,应该没有更改该状态的代码。
然而,你需要以某种方式暴露这种状态;不可变的只写对象不提供非常大的业务价值。将值类型实现为不可变公共属性的集合是一种常见的习惯用法。 Here's关于如何在PHP中执行此操作的旧讨论。或者,您可以实现允许Credentials对象将该状态的副本传递给其他对象的调用。
我应该为User类的$ credentials属性创建一个getter和setter吗?
通常不会 - 您的User实现具有Id属性,这使其看起来像一个实体。实体很少允许其内部状态逃脱。特别是,setter是很多9个,从来都不是一个好主意 - 实体的意思是它们可以强制执行自己的数据约束;他们有责任确保所有状态的变化都能满足业务不变性。
提供访问不可变状态的getter不太可能导致问题;允许调用者导航到可变状态的getter是令人担忧的。
但是:getter不是特别富有表现力 - 一个可能由域服务支持的查询通常是一个更灵活的选择
比较
password = user.password
strength = calulatatePasswordStrength(password.toString)
到
password = user.password
strength = password.strength(calculator)
到
strength = user.passwordStrength(calculator)
我应该将changePassword()方法放在User类中吗?
假设您支持该用例,是的。凭据值知道如何从旧凭证计算新的凭证状态;用户实体知道如何验证允许当前状态的用户以这种方式更改凭证。
(例如:一个天真的安全实现可能会跟踪上次更改凭据的时间,并且具有限制密码更改的策略,其中使用的正确策略取决于用户的其他属性。这对于凭据对象来说太过分了自己做。)
特别是,用户的密码实际上是Credentials值对象中的一堆状态这一事实是一个实现细节,不应该向调用User的代码公开。换句话说,如果用户正在实现接口IUser(隔离实现细节),那么我希望IUser承诺将使用changePassword($ password)方法。
答案 1 :(得分:0)
几乎我会说。值对象的关键是它是一个值。即您可以将它与另一个相同类型的对象进行比较,并确定它的值是否不同。在这种情况下,我希望能够看到一个凭证,其中username = x和password = x将“等于”另一个具有相同值的凭证。
这并不一定意味着你需要在田野上吸气。
我的博客上有关于这个主题的有趣代码kata。你可以在Why Private C# Variables are Not as Private as you Thought
找到它价值对象kata靠近底部。希望有所帮助。