我想将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'];
}
// ...
}
}
这对我来说也有点不自然。
关于如何正确实现一个非常大的价值对象的任何建议?
答案 0 :(得分:12)
large list of parameters的主要问题是可读性以及混淆参数的危险。您可以使用Builder pattern中所述Effective Java解决这些问题。它使代码更具可读性(特别是不支持命名和可选参数的语言):
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
)将导致运行时错误,而不是使用构造函数时的编译时错误。您还应该考虑使用更多的Value对象,例如PostalCode(与传递字符串相反)。
在相关说明中,有时业务需求要求更改值对象的一部分。例如,当最初输入地址时,街道号码可能拼写错误,现在需要更正。由于您将Address建模为不可变对象,因此没有setter。解决此问题的一种可能方法是在地址值对象上引入“无副作用函数”。该函数将返回对象本身的副本,但新的街道名称除外:
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);
}
...
}
答案 1 :(得分:0)
这是域驱动设计示例的常见问题。域专家遗失了,那个人会告诉你什么是地址及其要求。我怀疑域专家会告诉你一个地址没有一个Point。您可能能够从地址生成一个Point,但它不需要Point。也是P.O. Box在地址中不是单独的值。你可能需要一个邮政信箱地址类(POBoxAddress)我说这个,因为这个类看起来像是由开发人员定义的,而不是运输或账单域专家。通过与Domain Expert交谈,您可以减少构造函数参数计数。
第二
您可以开始将参数分组为值对象。您可以创建City值对象。这可能需要城市,地区/州和国家。除非我知道地区和国家,否则我认为城市名称并不重要。说巴黎只意味着巴黎,伊利诺伊,美国或巴黎,法兰西岛,FR为您提供完整的图片。因此,这也会减少Address对象的计数参数计数。
如果您在DDD路上找到您正在编码的域的域专家,则您不应该是专家。有时问题不应该通过代码或漂亮的设计模式来解决。
答案 2 :(得分:-2)
immutable适用于并发计算,无阻塞且无锁定,不可变为高性能和良好的可伸缩性。
所以Value Object可以在并发系统中运行得更好,包含在分发系统中,用新的VO替换旧的VO,不需要更新,所以没有阻塞。