从原始数据类型到值对象
本文转载自【何以解耦】:codedecoupled.com/value-object.htm...
生活中,我们使用数量词来描述事物的长度,重量,金额等等。在建模过程中,缺乏经验的开发者习惯使用原始数据类型(Primitive data type)给数量词造型。
比如行走的距离(使用 int
):
class Walker
{
private int $distance;
public function __construct(int $distance)
{
$this->distance = $distance;
}
}
比如产品的重量(使用 float
或者 int
):
class Goods
{
private int $weight;
public function __construct(int $weight)
{
$this->weight = $weight;
}
}
比如一个商品的价格(使用 float
或者 int
):
class Product
{
private int $price;
public function __construct(int $price)
{
$this->price = $price;
}
}
以上代码有几个问题:
量词单位被隐藏
距离
,重量
和 价格
作为数量词,量词部分的单位在代码中被隐藏了:
- 距离($distance)是米,千米还是英里?
- 重量($weight)是克,千克还是英镑?
- 价格($price)是人民币,美元还是欧币?
有漏洞的抽象(leaky abstraction)
当需要赋予以上数量词行为的时候,我们会发现使用原始数据类型建立的模型过于单薄,形成了一种几乎不具备内部细节的抽象。
比如我们需要赋予数量词相加行为:
- 距离($distance), 重量($weight),价格($price)相加之前,我们必须保证它们不能为负数。
- 距离($distance), 重量($weight),价格($price)相加之前,我们必须保证它们的单位统一。
这种有漏洞的抽象暴露了原始数据类型建模的局限性。
值对象
值对象是 DDD 战术设计中的一种建模工具。它是领域中用来描述,量化或者测量实体的模型。它是一种很简答的模式,不光是在 DDD 项目中,在其他项目中,值对象都是一种很强大的建模方式。
值对象的特点是具有不变性(Immutability),一旦创建以后,一个值对象的属性就定型了,不可更改。
我们可以使用值对象为上文中的数量词建模:
$distance
变为:
class Distance
{
private int $number;
private string $unit;
public function __construct(int $number, string $unit)
{
if ($number < 0) {
throw new DomainException('Invalid number');
}
$this->number = $number;
$this->unit = $unit;
}
public function add(Distance $distance)
{
if ($distance->unit != $this->unit) {
throw new DomainException('Invalid unit');
}
return new Distance(
$this->number + $distance->getNumber(),
$this->unit
);
}
public function getUnit(): string
{
return $this->unit;
}
public function getNumber(): int
{
return $this->number;
}
}
$weight
变为:
<?php
class Weight
{
private int $number;
private string $unit;
public function __construct(int $number, string $unit)
{
if ($number < 0) {
throw new DomainException('Invalid number');
}
$this->number = $number;
$this->unit = $unit;
}
public function add(Weight $weight)
{
if ($weight->unit != $this->unit) {
throw new DomainException('Invalid unit');
}
return new Weight(
$this->number + $weight->getNumber(),
$this->unit
);
}
public function getUnit(): string
{
return $this->unit;
}
public function getNumber(): int
{
return $this->number;
}
}
$price
变为:
<?php
class Price
{
private int $number;
private string $currency;
public function __construct(int $number, string $currency)
{
if ($number < 0) {
throw new DomainException('Invalid number');
}
$this->number = $number;
$this->currency = $currency;
}
public function add(Price $weight)
{
if ($weight->currency != $this->currency) {
throw new DomainException('Invalid currency');
}
return new Price(
$this->number + $weight->getNumber(),
$this->currency
);
}
public function getCurrency(): string
{
return $this->currency;
}
public function getNumber(): int
{
return $this->number;
}
}
仔细看一下以上代码,分析一下它是如何解决原始数据类型所带来的问题:
量词单位明确表达
量词的单位清晰地写入了代码中,类 Distance
,Weight
,Price
都具备专门的属性来表达量词的单位:$unit
和 $currency
。我们甚至可以写得更加深入,给量词单位建立独立的枚举类型。
完整的抽象
数量词的初始化条件,相加行为都完整的封装在了值对象中,作为使用者,可以放心地使用操作这些数量词,不需要去关心它们的内部细节。
总结
使用值对象进行建模可大大提高代码可读性,提升协同工作效率。同时为值对象编写单元测试也非常简单。
本文转载自【何以解耦】: codedecoupled.com/value-object.htm... ,如果你也对 TDD,DDD 以及简洁代码感兴趣,欢迎关注公众号【何以解耦】,一起探索软件开发之道。
本作品采用《CC 协议》,转载必须注明作者和本文链接