本文轉載自【何以解耦】: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 協議》,轉載必須註明作者和本文連結