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

news2024/9/22 1:26:00

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

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

DDD设计方法-2-聚合、实体、值对象(概览)

  • 1、概览
    • 1.1、值对象
        • 举例子:
    • 1.2、实体对象
        • 举例子:
    • 1.3、聚合
        • 举例子:
  • 答疑
    • 和之前的vo bo dto 到底有什么差别, 就是名称改了个叫法吗?
    • 聚合的实际作用是什么?为什么我要选择用聚合?

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);
    }
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2101822.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于mspm0g3507的智能送药小车(21年电赛f题,openmv寻迹,k210数字识别,并行pid调制)项目实验报告

2024年全国大学生电子设计竞赛&#xff08;TI杯&#xff09; 2024年7月17日 摘要&#xff1a;本项目由微处理器MSPM0G3507&#xff0c;编码器电机驱动&#xff0c;OPENMV、K210视觉处理单元&#xff0c;红外药品检测单元&#xff0c;ZIGBEE无限透传单元&#xff0c;OLED显示&am…

Docker数据卷和Dockerfile

1、什么是Docker数据卷 前言&#xff1a; 在下载的镜像中&#xff0c;我们不能够去改变它内部的一些配置&#xff0c;因为docker的镜像文件是已经配置好的&#xff0c;无法改变&#xff0c;我们只能改变镜像启动后的容器里面的内容&#xff0c;但是又因为&#xff0c;容器本来…

Java框架第四课(对Spring的补充Spring web)

目录 一.Spring web的认识 (1)Spring Web概念 (2)Spring web的特点 (3)Springweb运行的流程 (4)Springweb运行的流程图 二.搭建Spring web 三.自定义处理器类搭建 (1)处理器类配置 (2)处理器类接受请求 (3)获得请求数据 四.拦截器 (1)关于拦截器&#xff1a; (2)拦截器的…

【VMware】麒麟系统网络连接配置

在VMware配置页面点击编辑&#xff0c;进入虚拟网络编辑器将默认的 VMnet0删除&#xff0c;新建网络&#xff0c;设置桥接模式为Intel 打开主机cmd,查看主机IP地址&#xff0c;获取子网掩码&#xff0c;默认网关及DNS服务器 4.在主机寻找可用IP地址&#xff0c;ping不通的为未…

探秘发酵过程:酵母菌如何为白酒赋予不同风味?

在白酒酿造的神秘世界里&#xff0c;发酵过程如同一位隐形的艺术家&#xff0c;用其不同的笔触为白酒勾勒出千变万化的风味。而在这背后&#xff0c;酵母菌作为发酵的主角&#xff0c;发挥着至关重要的作用。今天&#xff0c;就让我们一起探秘发酵过程&#xff0c;了解酵母菌如…

shell 学习笔记:变量、字符串、注释

目录 1. 变量 1.1 定义使用变量 1.2 变量命名规则 1.3 只读变量 1.4 删除变量 1.5 变量类型 1.5.1 字符串变量 1.5.2 整数变量 1.5.3 数组变量 1.5.3.1 整数索引数组 1.5.3.2 关联数组 1.4 环境变量 1.5 特殊变量 2. 字符串 2.1 单引号字符串 2.2 双引…

erlang学习:用OTP构建系统23.12练习题

练习要求 制作一个名为prime_tester_server的gen_server&#xff0c;让它测试给定的数字是否是质数。 你可以使用lib_primes.erl里的is_prime/2函数来处理&#xff08;或者自己实现一个更好的质数测试函 数&#xff09;。把它添加到sellaprime_supervisor.erl的监控树里。 质…

图论(2)

一、度 度统计的是一个节点上又多少条边 度出度入度 出度&#xff1a;统计以该节点为起始点箭头指向外面的边的条数 入度&#xff1a;统计箭头指向该节点的边数 度为1的节点为悬挂节点&#xff0c;边为悬挂边 用矩阵计算节点的度 二、握手定理 比如这里第一个集合里面有三…

ARP协议(原理,特点,报文格式,具体过程),ARP缓存(有效时间,为什么),ARP欺骗(定向断网,成为中间人),RARP简单介绍

目录 ARP协议 引入 介绍 原理 arp请求/响应 特点 报文格式 硬件类型 协议类型 硬件/协议地址长度 op(操作码) 过程 发送请求并处理 返回响应并处理 总结 arp缓存 介绍 arp表项的有效时间 解释 arp欺骗 介绍 定向断网 基于arp的成为中间人的方式 多向…

跟李沐学AI:序列模型

目录 序列数据 自回归模型 马尔可夫假设 潜变量模型 序列模型总结 序列数据 实际中很多数据是时序结构的&#xff0c;如&#xff1a;电影的评价随时间的变化而变化&#xff1a;拿奖后评分上升、电影整体质量提升&#xff0c;人们要求变高。。。等等 除此之外&#xff0c;音…

比特币网络和支付

1. 比特币网络 比特币网络是一个去中心化的点对点网络&#xff0c;节点之间可以直接进行交易。网络上有不同类型的节点。 1.1 比特币网络的节点 比特币网络的节点有两种主要类型&#xff1a;全节点也称为完整节点和简单支付验证&#xff08;Simple Payment Verification,SPV)节…

档案|基于SprinBoot+vue的档案管理系统(源码+数据库+文档)

档案管理系统 基于SprinBootvue的档案管理系统 一、前言 二、系统设计 三、系统功能设计 管理员功能模块实现 学生功能模块实现 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农…

【数据库|第11期】深入掌握 SQL Server、Access 与 SQLite 中的 `UNION` 与 `UNION ALL`:从理论到实践

日期&#xff1a;2024年9月3日 作者&#xff1a;Commas 签名&#xff1a;(ง •_•)ง 积跬步以致千里,积小流以成江海…… 注释&#xff1a;如果您觉得有所帮助&#xff0c;帮忙点个赞&#xff0c;也可以关注我&#xff0c;我们一起成长&#xff1b;如果有不对的地方&#xff…

EMC整改问题

定位问题: 1.控制变量比较法:连和不连&#xff0c;接和不接来判断 2.频率判断法:低频一般是电源&#xff0c;高频一般是信号或者无线通信问题&#xff0c;还有倍频问题 3.解决方法: a.加器件&#xff0c;滤波&#xff0c;EMI共模电感&#xff0c;磁环 b.电源&#xff0c;高速信…

App推广新篇章:Xinstall带你走出数据迷雾,实现高效推广!

在如今的移动互联网时代&#xff0c;App推广已成为每个应用开发者必须面对的重要课题。然而&#xff0c;推广过程中往往伴随着诸多痛点&#xff0c;如数据混乱、投放盲目、决策滞后以及作弊困扰等。这些问题不仅影响了推广效果&#xff0c;还可能导致资源的浪费和投入产出不均衡…

Java版本的扫雷游戏程序

一、开发环境 开发工具:eclipse2021-12 JDK版本:JDK15.0.1 二、运行效果展示 这张图是游戏刚开始的画面,重置以后也是这个画面 此图是写代码的过程调试用的画面,方便查找问题。 此图是运行过程中的图片

实习的一点回顾Webhook的执行

1.Webhook流程 1.Bass外的部分 比如我通过控制台或者js脚本去调用curl命令call指定的webhook的地址的功能脚本 命令发送到网关&#xff0c;网关通过注册中心之类的发送到服务实体上。 这些是微服务的东西 2.OpenAPI到Controller阶段 先看之前openAPI的那篇前置 请求进来之…

Anaconda的环境管理操作命令详解-学习篇

一、通过命令方式管理环境 1. 查看环境 使用以下命令查看当前所有环境的命令conda env list可以看到目前电脑的base环境情况&#xff0c;我的本机只有一个base环境。是anaconda3在安装的时候所选的根目录信息。命令前的(base) 代表目前执行处于base环境&#xff0c;* 代表目前…

高德地图-小米14 Pro 定制版 v12.10.61.3021 简洁版

高德地图小米14 Pro 定制版是一款专门为小米14 Pro 设计的简洁版高德地图。相较于普通版本&#xff0c;该版本体积更小&#xff0c;运行速度更快&#xff0c;并且没有广告。支持驾车、骑行、公交地铁、步行等多种导航模式&#xff0c;使用北斗卫星导航系统&#xff0c;精准度非…

从零开始使用 LangGraph、LLaMA3 和 Elasticsearch 向量存储构建本地代理的教程

作者&#xff1a;来自 Elastic Pratik Rana 在本教程中&#xff0c;我们将了解如何使用 LangGraph、LLaMA3 和 Elasticsearch Vector Store 从头开始​​创建可靠的代理。我们将结合 3 篇高级 RAG 论文中的想法&#xff1a; 用于路由的自适应 RAG&#xff1a;根据内容将问题引…