DDD设计方法-2-聚合、实体、值对象

前情提要:一共包含 如下六篇文章(篇幅精简,快速入门)

1、初识DDD
2、聚合、实体、值对象
3、仓储,封装持久化数据
4、端口和适配器
5、领域事件
6、领域服务,实现约定

1、概览

在软件工程和领域驱动设计(DDD)中,聚合、实体和值对象是三个重要的概念,它们帮助我们构建清晰且有组织的领域模型。以下是对这三个概念的简要概览和示例;

介绍的时候首先带入一个场景: 如下例子基于 户口管理 系统举例

1.1、值对象

在这里插入图片描述

首先要知道值对象的特点是什么?然后你才能在程序之中使用;
1、不变;一旦被建立不应该被修改,而应该重新创建
2、没有唯一标识符
3、如何此值对象每一个属性值都相等,那么认为对象相等;	
举例子:

地址 是一个值对象,通过其属性(如省、市、区县、街道)进行比较和区分,通常是不可变的。

public final class Address {
	//省
    private final String province;
    //地市
    private final String city;
    //区县
    private final String district;
    //乡镇
    private final String town;

    // 构造函数
    public Address(String province, String city, String district, String town) {
        this.province = province;
        this.city = city;
        this.district = district;
        this.town = town;
    }
}

1.2、实体对象

在这里插入图片描述

首先要知道实体对象的特点是什么?然后你才能在程序之中使用;
1、状态和属性可能会发生变化,但是标识符不变。
2、必有唯一标识符
3、通常代表现实世界中的可变对象。

白话文: 实体 = 唯一标识 + 状态属性 + 行为动作(功能)

举例子:
	每个人都有唯一标识符(例如身份证号)。
	人的属性可能会变化(如姓名、性别、出生日期等)。
public class Person {
    private final String id; // 身份证号或其他唯一标识符
    private String name;
    private String gender;
    private LocalDate birthDate;

    public Person(String id, String name, String gender, LocalDate birthDate) {
     if (id == null || id.isEmpty()) {
            throw new IllegalArgumentException("ID cannot be null or empty");
        }
        if (name == null || name.isEmpty()) {
            throw new IllegalArgumentException("Name cannot be null or empty");
        }
        if (!"男".equals(gender) && !"女".equals(gender)) {
            throw new IllegalArgumentException("Gender must be '男' or '女'");
        }
        if (birthDate == null || birthDate.isAfter(LocalDate.now())) {
            throw new IllegalArgumentException("Birth date cannot be null or in the future");
        
        this.id = id;
        this.name = name;
        this.gender = gender;
        this.birthDate = birthDate;
    }



}

1.3、聚合

首先要知道聚合的特点是什么?然后你才能在程序之中使用;
1、包含多个实体(Entity)和/或值对象(Value Object)。
2、其中一个实体被称为聚合根(Aggregate Root) ,它是聚合的唯一入口点。
3、通过聚合根来管理聚合的生命周期和一致性。
举例子:

户口本 作为一个聚合,包含了多个人实体和一个地址值对象。户口本负责管理其内部的实体,并保证其内部状态的一致性。

public class Household {
 
    private final String householdId;
    private final List<Person> people;
    private final Address address;

    public Household(String householdId, Address address) {
        this.householdId = householdId;
        this.address = address;
        this.people = new ArrayList<>();
    }
}

使用示例:

public class Test{
    public static void main(String[] args) {
        // 创建地址值对象
        Address address = new Address("北京市", "北京市", "海淀区", "中关村大街");

        // 创建户口本聚合
        Household household = new Household("HH12345", address);

        // 创建人实体
        Person person1 = new Person("ID1234567890", "张三", "男", LocalDate.of(1985, 1, 1));
        Person person2 = new Person("ID0987654321", "李四", "女", LocalDate.of(1990, 5, 15));

        // 将人添加到户口本
        household.addPerson(person1);
        household.addPerson(person2);

        // 打印户口本信息
        System.out.println(household);
    }
}

答疑

到这里是不是已经有人疑惑了?

和之前的vo bo dto 到底有什么差别, 就是名称改了个叫法吗?

DDD设计方法可以说是针对于业务切割和重新建模的方法。
比较大的区别就是建议在对象建立时对对象的合理性进行校验(这个是针对本章片面的一个说法)

比如说 : 针对上述例子: Person 人进行改造在对象创建时检验其合理性

 public Person(String id, String name, String gender, LocalDate birthDate) {
        if (id == null || id.isEmpty()) {
            throw new IllegalArgumentException("ID cannot be null or empty");
        }
        if (name == null || name.isEmpty()) {
            throw new IllegalArgumentException("Name cannot be null or empty");
        }
        if (!gender.equals("男") && !gender.equals("女")) {
            throw new IllegalArgumentException("Gender must be '男' or '女'");
        }
        if (birthDate == null || birthDate.isAfter(LocalDate.now())) {
            throw new IllegalArgumentException("Birth date cannot be null or in the future");
        }
        this.id = id;
        this.name = name;
        this.gender = gender;
        this.birthDate = birthDate;
    }

这样可以在使用的时候保证无需其他额外校验,你拿到这个对象这个对象的值就是合理的。

聚合的实际作用是什么?为什么我要选择用聚合?

针对上边户口本聚合做实际上使用时添加微小功能
1、确保户口本中的人员不能为空,并且必须有一个且只有一个户主。
2、添加和删除成员:可以添加和删除除户主之外的成员。
3、户主转换功能:可以将现有成员转换为新的户主,原户主变为普通成员。

这样你在使用聚合的时候

  1. 变更入口统一
  2. 业务规则和校验集中 :

    例如,添加成员时检查成员是否已存在、删除成员时检查是否尝试删除户主、更换户主时检查新户主是否为现有成员等。这些校验确保了数据的一致性和完整性。

  3. 减少耦合,提高内聚
  4. 确保数据一致性 :

通过统一的入口进行变更,可以更好地确保数据的一致性。例如,在更换户主时,如果不通过聚合根进行,而是直接修改数据,可能会导致数据不一致的问题。而通过聚合根的方法来进行这些操作,可以确保所有相关的业务逻辑和校验都得到执行,从而保证数据的一致性。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

public class Household {
    private final String householdId;
    private final Address address;
    private final List<Person> members;
    private Person head;

    public Household(String householdId, Address address, Person head) {
        if (householdId == null || householdId.isEmpty()) {
            throw new IllegalArgumentException("Household ID cannot be null or empty");
        }
        if (address == null) {
            throw new IllegalArgumentException("Address cannot be null");
        }
        if (head == null) {
            throw new IllegalArgumentException("Head of household cannot be null");
        }
        this.householdId = householdId;
        this.address = address;
        this.head = head;
        this.members = new ArrayList<>();
    }

    public void addMember(Person person) {
        if (person == null) {
            throw new IllegalArgumentException("Person cannot be null");
        }
        if (members.contains(person) || head.equals(person)) {
            throw new IllegalArgumentException("Person is already a member of the household");
        }
        members.add(person);
    }

    public void removeMember(Person person) {
        if (person == null || head.equals(person)) {
            throw new IllegalArgumentException("Cannot remove the head of household or a null person");
        }
        members.remove(person);
    }

    public void changeHead(Person newHead) {
        if (newHead == null) {
            throw new IllegalArgumentException("New head of household cannot be null");
        }
        if (!members.contains(newHead)) {
            throw new IllegalArgumentException("New head of household must be a current member");
        }
        members.add(head); // 将当前户主降为普通成员
        members.remove(newHead); // 从普通成员中移除新户主
        this.head = newHead;
    }

    ....

    public List<Person> getMembers() {
        return Collections.unmodifiableList(members);
    }
}

附:改造后的测试方法

import java.time.LocalDate;

public class Test {
    public static void main(String[] args) {
        // 创建地址值对象
        Address address = new Address("北京市", "北京市", "海淀区", "中关村大街");

        // 创建人实体
        Person head = new Person("ID1234567890", "张三", "男", LocalDate.of(1985, 1, 1));
        Person member1 = new Person("ID0987654321", "李四", "女", LocalDate.of(1990, 5, 15));
        Person member2 = new Person("ID1112131415", "王五", "男", LocalDate.of(1992, 7, 20));

        // 创建户口本聚合
        Household household = new Household("HH12345", address, head);

        // 添加成员
        household.addMember(member1);
        household.addMember(member2);

        // 打印户口本信息
        System.out.println(household);

        // 更换户主
        household.changeHead(member1);

        // 打印更换户主后的户口本信息
        System.out.println(household);

        // 删除成员
        household.removeMember(member2);

        // 打印删除成员后的户口本信息
        System.out.println(household);
    }
}

在这里插入图片描述

  • 14
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值