我想将Address
建模为值对象。由于使其不可变是一种良好的实践,因此我选择不提供任何setter,因为它可能允许以后修改它。
一种常见的方法是将数据传递给构造函数;但是,当值对象非常大时,可能会变得非常臃肿:
class Address {
public function __construct(
Point $location,
$houseNumber,
$streetName,
$postcode,
$poBox,
$city,
$region,
$country) {
// ...
}
}
另一种方法是将参数作为数组提供,从而产生一个干净的构造函数,但这可能会混淆构造函数的实现:
class Address {
public function __construct(array $parts) {
if (! isset($parts['location']) || ! $location instanceof Point) {
throw new Exception('The location is required');
}
$this->location = $location;
// ...
if (isset($parts['poBox'])) {
$this->poBox = $parts['poBox'];
}
// ...
}
}
在我看来也有点不自然。
关于如何正确实现一个相当大的值对象有什么建议吗?
大参数列表的主要问题是可读性和混淆参数的危险。您可以使用Effective Java中描述的Builder模式来解决这些问题。它使代码更具可读性(特别是那些不支持命名参数和可选参数的语言):
public class AddressBuilder {
private Point _point;
private String _houseNumber;
// other parameters
public AddressBuilder() {
}
public AddressBuilder WithPoint(Point point) {
_point = point;
return this;
}
public AddressBuilder WithHouseNumber(String houseNumber) {
_houseNumber = houseNumber;
return this;
}
public Address Build() {
return new Address(_point, _houseNumber, ...);
}
}
Address address = new AddressBuilder()
.WithHouseNumber("123")
.WithPoint(point)
.Build();
的优点:
- 参数的命名使其更具可读性
- 更难混淆房号和地区
- 可以使用自己的参数顺序
- 可选参数可省略
我能想到的一个缺点是忘记指定一个参数(例如不调用WithHouseNumber
)将导致运行时错误,而不是在使用构造函数时出现编译时错误。您还应该考虑使用更多的值对象,例如PostalCode(而不是传递字符串)。
public class Address {
private readonly String _streetName;
private readonly String _houseNumber;
...
public Address WithNewStreetName(String newStreetName) {
// enforce street name rules (not null, format etc)
return new Address(
newStreetName
// copy other members from this instance
_houseNumber);
}
...
}
这是领域驱动设计示例的常见问题。域名专家缺失了,他是告诉你什么是地址及其要求的人。我怀疑域名专家会告诉你地址没有点。你也许能够从一个地址中产生一个点,但它不需要一个点。此外,邮政信箱不会是地址中的单独值。您可能需要Post Office Box地址类(POBoxAddress)。我之所以这么说,是因为这个类看起来像是由开发人员定义的,而不是Shipping或Billing Domain Expert。通过与域专家交谈,您可以减少构造函数参数计数。
2日您可以开始将参数分组为值对象。您可以创建City值对象。这可能需要城市、地区/州和国家。我认为一个城市的名字没有多大意义,除非我知道这个地区和国家。说巴黎意味着巴黎,伊利诺伊州,美国或巴黎,Île-de-France, FR给你一个完整的画面。所以这也会减少Address对象的count参数计数。
如果你沿着DDD的道路找到一个领域专家为你正在编码的领域,你不应该是专家。有时问题不应该由代码或一个漂亮的设计模式来解决。
不可变适合并发计算,无阻塞和无锁,不可变是为了高性能和良好的可扩展性。
所以Value Object可以在并发系统中更好的运行,包括在分布式系统中,用新的VO替换旧的VO,不需要更新,所以没有阻塞。