DDD领域驱动设计-值对象

通过对象属性值来识别的对象,它将多个相关属性组合为一个概念整体

应该尽量使用值对象建模而非实体对象。即便一个领域概念必须建模成实体,在设计时也应更偏向于将其作为值对象容器,而非子实体容器。因为可以非常容易对值对象进行创建、测试、使用、优化和维护。

值对象的特征

当你决定一个领域概念是否是一个值对象时,需考虑它是否拥有以下特征:

  • 度量或者描述了领域中的一件东西

  • 可以作为不变量

  • 将不同的相关的属性组合成一个概念整体(Conceptual Whole)

  • 当度量和描述改变时,可以用另一个值对象予以替换

  • 可以和其他值对象进行相等性比较

  • 不会对协作对象造成副作用

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

在使用这种方法分析模型时,会发现很多领域概念都可设计成值对象,而非实体对象。

在设计得当时,我们可创建和传递值对象实例,甚至在用完后直接扔了。不用担心客户端对值对象的修改。一个值对象的生命周期可长可短,就像个无害的过客在系统中来往。 从该角度来看待值对象是个很大转变,就像从没有GC的语言转变到有GC语言。

值对象本质上就是一个集。该集合有若干用于描述目的、具有整体概念和不可修改的属性。该集合存在的意义是在领域建模的过程中,值对象可保证属性归类的清晰和概念的完整性,避免属性零碎。

不同状态的值对象

业务形态

值对象是DDD领域模型中的一个基础对象,跟实体一样源于事件风暴所构建的领域模型,都包含若干属性,与实体一起构成聚合。

  • 实体是看得到、摸得着的实实在在的业务对象,实体具有业务属性、业务行为和业务逻辑。

  • 值对象只是若干个属性集合,只有数据初始化操作和有限的不涉及修改数据的行为,基本不包含业务逻辑。值对象的属性集虽然在物理上独立,但在逻辑上仍是实体属性的一部分,用于描述实体的特征。

值对象中也有部分共享的标准类型的值对象,它们有自己的限界上下文及持久化对象,可建立共享的数据类微服务,比如数据字典。

代码形态

代码中有两种形态。如果值对象是

  1. 单一属性,直接定义为实体类的属性

  2. 属性集合,设计为Class类,Class将具有整体概念的多个属性归集到属性集合,这样的值对象没有ID,会被实体整体引用

运行形态

实体实例化后的DO对象的业务属性/行为都非常丰富,但值对象实例化的对象相对简单。除了值对象数据初始化和整体替换的行为外,很少有其它业务行为。

DB形态

设计值对象是期望转“数据建模为中心”为“领域建模为中心”,以减少数据库表的数量和表与表间复杂依赖,尽可能简化DB设计,提升DB性能。

代码示例

token值对象:

/**
 * token值对象
 *
 * @author haoxin
 * @date 2021-02-02
 **/
public final class Token implements ValueObject<Token> {

    /**
     * token
     */
    private String token;

    /**
     * 有效周期
     */
    private int expirePeriod;

    /**
     * 过期时间
     */
    private Date expireTime;



    public Token(String token, Date expireTime) {
        if(StringUtils.isEmpty(token)) {
            throw new IllegalArgumentException("token不能为空");
        }
        this.token = token;
        this.expireTime = expireTime;
    }

    public Token(String token, Date expireTime, int expirePeriod) {
        if(StringUtils.isEmpty(token)) {
            throw new IllegalArgumentException("token不能为空");
        }
        this.token = token;
        this.expireTime = expireTime;
        this.expirePeriod = expirePeriod;
    }

    /**
     * 创建Token
     */
    public static Token create(String tokenStr) {
        //12小时后过期
        int expirePeriod = 3600 * 12;
        //当前时间
        Date now = new Date();
        //过期时间
        Date expireTime = new Date(now.getTime() + expirePeriod * 1000);
        return new Token(tokenStr,expireTime, expirePeriod);
    }

    public String getToken() {
        return token;
    }

    public Date getExpireTime() {
        return expireTime;
    }

    public int getExpirePeriod() {
        return expirePeriod;
    }

    @Override
    public boolean sameValueAs(Token other) {
        return other != null && this.token.equals(other.token);
    }

    @Override
    public String toString() {
        return "Token{" +
                "token='" + token + '\'' +
                ", expirePeriod=" + expirePeriod +
                ", expireTime=" + expireTime +
                '}';
    }
}

密码值对象


/**
 * 密码
 *
 * @author haoxin
 * @date 2021-02-08
 **/
public class Password implements ValueObject<Password> {

    public static final String DEFAULT = "123456";

    /**
     * 密码
     */
    private String password;

    /**
     * 盐
     */
    private String salt;

    public Password(String password, String salt) {
        if(StringUtils.isEmpty(password)) {
            throw new IllegalArgumentException("密码不能为空");
        }
        this.password = password;
        this.salt = salt;
    }

    public static Password create(String passwordStr) {
        String salt = RandomStringUtils.randomAlphanumeric(20);
        String password = new Sha256Hash(passwordStr, salt).toHex();
        return new Password(password, salt);
    }

    public static Password create(String passwordStr, String salt) {
        if(passwordStr.length() < 6) {
            throw new IllegalArgumentException("密码长度不能小于6");
        }
        String password = new Sha256Hash(passwordStr, salt).toHex();
        return new Password(password, salt);
    }

    public String getPassword() {
        return password;
    }

    public String getSalt() {
        return salt;
    }

    @Override
    public boolean sameValueAs(Password other) {
        return other != null && this.password.equals(other.password);
    }

    @Override
    public String toString() {
        return "Password{" +
                "password='" + password + '\'' +
                ", salt='" + salt + '\'' +
                '}';
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

haoxin963

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值