实现领域驱动设计(DDD)-值对象的特征、使用及实现

作者: admin 分类: 领域驱动设计 发布时间: 2019-11-28 18:12  阅读: 54 views

值对象的常见例子包括数字、文本字符串、日期、时间、某人的全称、货币、电话号码、邮寄地址等。这种类型的数据用于度量和描述事物,可以非常容易地对值对象进行创建、测试、使用、优化和维护。

我们应该尽量使用值对象来建模而不是实体对象,这样可以非常容易地对值对象进行创建、测试、使用、优化和维护。

在设计得当的情况下,我们可以对值对象实例进行创建和传递,甚至在使用完之后可以直接扔掉。不用担心客户端对值对象的修改。一个值对象的生命周期可长可短,就像一个无害的过客在系统中来来往往。从这个角度来看值对象是一个很大的转变,就像从没有垃圾回收机制的语言转变到有垃圾回收机制的语言一样。

当你只关心某个对象的属性时,该对象便可作为一个值对象。为其添加有意义的属性,并赋予它响应的行为。我们需要将值对象看成不变对象,不要给它任何身份标识、还应该尽量避免像实体对象一样的复杂性。

关于值对象的特征

当决定一个领域概念是否是一个值对象时,需要考虑它是否拥有以下特征
1. 它度量或者描述了领域汇中的一件东西。
2. 它可以作为不变量。
3. 它将不同的相关的属性组合成一个概念整体。
4. 当度量和描述改变时,可以用另一个值对象予以替换。
5. 它可以和其他值对象进行相等性比较。
6. 它不会对协作对象造成副作用。

度量或描述

当你的模型中的确存在一个值对象时,它不是领域中的一件东西(实体、对象),而只是用于度量或描述领域中某件东西的一个概念。比如人拥有年龄、姓名,它们只是度量或描述,而不是领域对象。

不变性

在值对象初始化之后,任何方法都不能对该对象的属性状态进行修改。

public final class BusinessPriority implements Serializable{

    private BusinessPriorityRatings ratings;

    public BusinessPriority(BusinessPriorityRatings aratings){
        super();
        this.setRatings(aratings);
        this.initialize();
    }
    ...    
}

概念整体

一个值对象可以只处理单个属性,也可以处理一组关联的属性。

如对 (公司值50,000,000美元)进行建模,不正确(从值对象角度出发)的方式如下:

public class ThingOfWorth{
    private String name;        //商品描述
    private BigDecimal amount;  //金额描述
    private String currency;    //货币描述
}

正确的方式如下:

public class ThingOfWorth{
    private ThingName name;      //名称,可以拓展为中、英文,或者简称、简介等
    private MonetaryValue worth; //资产属性MonetaryValue

}

//针对金额、货币
public final class MonetaryValue implements Serializable{
    private BigDecimal amount;
    private String currency;
    private MonetaryValue(BigDecimal anAmount,String aCurrency){
        this.setAmount(anAmount);
        this.setCurrency(aCurrency);
    }
}


## 值对象的构造函数用于保证概念整体的有效性和不变性。最好是对象的构造函数一次性构建好整个值对象。

对于值对象的验证,应该和实体属性的验证一致。放到这一层,某些情况可以减少代码量(没有业务验证的情况)

可替换性

int total = 3;

//稍后
total = 4;

值对象相等性

如果两个对象的类型和属性都是相等的,那么这两个对象也是相等的。可以用其中一个实例来替换另一个实例。

无副作用行为

一个对象的方法可以设计成一个无副作用函数。这里的函数表示对某个对象的操作,它只用于产生输出,而不会修改对象的状态。由于在函数执行的过程中没有状态改变,这种函数操作成为无副作用函数。如下:

FullName name = new FullName("Vaughn","Vernon");
//处理

name = name.withMiddleInitial("L"");



//无副作用函数,没有修改其他值对象    
public FullName withMiddleIntitial(String aMiddleNameOrInitial){
    if(aMiddleNameOrInitial == null){
            throw exception
    }
    String middle = aMiddleNameOrInitial.trim();

    return new FullName(this.firstName(),middle.substring(0,1).toUpperCase(),this.lastName());
}

最小化集成

在所有的DDD项目中,通常存在多个限界上下文,需要进行集成。当模型概念从上游到下游是,尽量使用值对象来表示这些概念。这样的好处是可以达到最小化集成,使用不变的值对象使得我们做更少的职责假设。

用值对象表示标准类型

标准类型是用于标识事物类型的描述性对象。系统中既有标识事物的实体和描述实体的值对象,同时还存在标准类型来区分不同的类型。如使用枚举表示标准类型

public enum GroupMemberType{
    GROUP{
        public boolean isGroup(){
            return true;
        }
    },
    USER{
        public boolean isUser(){
            return true;
        }
    }

    public boolean isGroup(){
        return false;
    }
    public boolean isUser(){
        return false;
    }
}

通过 
GroupMemberType.USER 来使用

我们一般采用JavaBean规范创建对象,其中有公用的setter方法,这种方式违背了值对象的不变性特征。

看完这章,感觉值对象是对entity类中的具体属性的定义。不过从领域对象的角度去分析,这样设计还是有很多好处的。前提是对整体的需求、需要有较深的了解


   原创文章,转载请标明本文链接: 实现领域驱动设计(DDD)-值对象的特征、使用及实现

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

电子邮件地址不会被公开。 必填项已用*标注

更多阅读