領域知識與模型 - 實體與實值物件

領域模型並非全由實體所組成,針對某些不需要特別追蹤的資料,我們傾向透過建立實值物件(Value Object)支援相關的實作,而這個特性也是實值物件與實體最大的差異,當我們建立一個實值物件,目的在於封裝特定的值,並配置為某個實體的一部份,本身不需要作識別,因此不需要特別為這個物件配置如ProductID之類的識別資訊。

以討論實體列舉的Product實體為例,商品還會記錄包含如顏色或是長寬等規格資訊,這些資訊屬於商品的一部份,例如以下的Product實體:
public class Product
{
    public int ProductID { get; set; }   // 識別ID
    public string ProductName { get; set; } // 商品名稱
    public decimal UnitPrice { get; set; }  // 單價
    public string Description { get; set; } // 商品描述
    public PColor ProductColor { get; set; } // 商品規格-顏色
    public PSize ProductSize { get; set; }  // 商品規格-大小
}
public enum PColor
{
    Red = 100,
    Blue = 200,
    Green = 300,
    Black = 4100
}
public enum PSize
{
    XL = 10,
    L = 100,
    M = 200,
    S = 300,
    SS = 400
}
屬性ProductColor 表示商品規格的顏色,是一個PColor列舉型別,而ProductSize則表示商品規格的大小,是一個PSize列舉型別,在實務的設計上,這兩個屬性都表示商品的規格資訊,我們可以進一步將其整合在獨立的物件中。

另外定義一個類別 Pspec,於其中配置兩組規格屬性,分別是ProductColor與ProductSize:
public class Pspec
{
    public Pspec(PColor color, PSize size)
    {
        ProductColor = color;
        ProductSize = size;
    }
    public PColor ProductColor { get; }
    public PSize ProductSize { get; }
}
ProductColor是PColor型別,儲存關於顏色的資訊,而ProductSize是儲存關於尺寸的資訊,接下來重新調整Product結構如下:
public class Product
{
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public decimal UnitPrice { get; set; }
    public string Description { get; set; }
    public Pspec ProductSpec { get; set; }
}
原來與規格有關的部份,直接以Pspec型別的ProductSpec設定。

以上的調整進一步簡化Product類別設計,現在當我們建立一個Product物件時,必須另外建立Pspec物件以封裝規格資訊,並且將其設定給ProductSpec屬性。



左圖是原來的類別結構,ProductColor 與 ProductSize抽離出來定義於 Pspec 類別,而Product類別則進一步簡化其結構設計。

以上的調整進一步簡化Product類別設計,現在當我們建立一個Product物件時,必須另外建立Pspec物件以封裝規格資訊,並且將其設定給ProductSpec屬性。

Pspec本身僅封裝特定的資訊值,這是一個典型的實值物件,應用程式並不會特別追蹤這個物件,不需要識別,因此不會有識別屬性,由於這些特性,可以很容易建立實值物件,而當應用程式需要調整實值物件的內容時,通常是直接建立一個新的物件取代舊物件,而不是去修改其中的屬性內容,在上述的Pspec類別中,你可以看到其中建立的是唯讀屬性,並且於建構式完成屬性的初始化,如此一來,這個物件一旦建立之後,就沒辦法更改了。

無法更改的實值物件表示其封裝值是固定的,因此被共用在不同的實體是可以接受的,同時也可以省下不需要的資源浪費,例如當數個Product實體具相同的規格,可以同時將相同的實值物件,指定給這些Product實體的規格屬性,如果其中一個實體的規格發生改變,我們將選擇建立另外一個新的規格物件進行置換,不影響其它實體的規格值。

儘管實值物件不如實體具有描述領域問題的特性,在設計上依然要特別注意,避免將所有屬性值全部由單一實值物件進行列舉,不同性質的值必須另外建立不同的實值物件義,例如一般常見的地址資訊,也適合以實值物件封裝,當我們要處理這一類的資料時,比較好的作法是另外再建立一個新的類別,例如AddressInfo,支援相關的資料處理。
public class AddressInfo
{
    public int Region;
    public int Stnumber;
    public string Street;
    public string City;
}
AddressInfo封裝的是地址資訊,而上述的Pspec則是商品規格資訊,我們在需要的時候,使用適當的實值物件,

實體與實值類別的設計,我們進一步利用 Entity Framework 實踐相關的實作,歡迎參考《商業級 ASP.NET MVC 樣式與架構實務





沒有留言: