领域驱动设计(DDD)【23】之泛化:从概念到实践

文章目录

  • 一 泛化基础:理解DDD中的核心抽象机制
    • 1.1 什么是泛化?
    • 1.2 为什么泛化在DDD中重要?
    • 1.3 泛化与特化的双向关系
  • 二 DDD中泛化的实现形式
    • 2.0 实现形式概览
    • 2.1 类继承:最直接的泛化实现
    • 2.2 接口实现:更灵活的泛化方式
    • 2.3 组合优于继承:现代DDD的实践
    • 2.4 特征值实现:马匹管理
  • 三 DDD中泛化的高级应用
    • 3.1 限界上下文中的泛化
    • 3.2 泛化的层次设计
    • 3.3 泛化与持久化的权衡
  • 四 避免泛化的常见陷阱
    • 4.1 过度泛化:当抽象变成负担
    • 4.2 错误泛化:混淆本质差异
    • 4.3 泛化导致的数据污染
  • 五、实战案例:电商系统中的泛化设计
    • 5.1 商品类别的泛化设计
    • 5.2 订单处理的状态模式实现
  • 六 总结与最佳实践
    • 6.1 何时使用泛化?
    • 6.3 泛化的替代方案
    • 6.4 持续重构的视角

一 泛化基础:理解DDD中的核心抽象机制

  • 在领域驱动设计(Domain-Driven Design, DDD)中,泛化(Generalization)是一种强大的抽象工具,它允许我们识别和表达领域概念之间的"是一种"(is-a)关系。

1.1 什么是泛化?

  • 泛化是指从多个特定概念中提取其共性,形成更一般化的概念的过程。在面向对象编程中,这通常通过继承来实现,但在DDD中,泛化的含义更为广泛。现实世界类比:想象你在设计一个动物园管理系统,特定概念:狮子、老虎、长颈鹿->泛化概念:动物。这里,"狮子是一种动物"就体现了泛化关系。

1.2 为什么泛化在DDD中重要?

  1. 减少重复:共性行为可以放在父类中
  2. 提高可维护性:修改一处即可影响所有子类
  3. 增强表达力:更准确地反映业务领域的关系
  4. 支持多态:允许以统一方式处理不同子类

1.3 泛化与特化的双向关系

Animal
+String name
+Date birthDate
+eat()
+sleep()
Lion
+roar()
Tiger
+growl()
  • 在这个类图中,Animal是泛化类,Lion和Tiger是特化类。所有动物都有name和birthDate属性,都能eat()和sleep(),但只有狮子能roar(),只有老虎能growl()。

二 DDD中泛化的实现形式

2.0 实现形式概览

在这里插入图片描述
第一种是使用类的继承。领域模型中的父类、子类,和实现中的父类、子类直接对应。它的特点在于必须有不同种类的特性或者不同的操作。如果仅仅是某个属性值不同造成的泛化,那么用继承就不合适了。
第二种是接口的实现。如果各个子类在属性和操作实现方面没有共性,但有相同的操作接口,就使用这种方式。
第三种是用特性的值来区分,这时候在实现层面,没有父类和子类之分。特别适用于那些行为完全相同,仅在某些属性值上存在差异的领域概念。

2.1 类继承:最直接的泛化实现

  • 电商系统示例
public abstract class Payment {private BigDecimal amount;private Currency currency;private LocalDateTime paymentDate;public abstract void process();
}public class CreditCardPayment extends Payment {private String cardNumber;private String cardHolder;private LocalDate expiryDate;@Overridepublic void process() {// 信用卡支付处理逻辑}
}public class PayPalPayment extends Payment {private String email;private String transactionId;@Overridepublic void process() {// PayPal支付处理逻辑}
}

优劣分析

  • 优点:直接、明确、编译器支持
  • 缺点:Java等语言单继承限制、父类修改影响大

2.2 接口实现:更灵活的泛化方式

public interface Shipment {void schedule();void cancel();TrackingInfo track();
}public class StandardShipment implements Shipment {// 实现标准物流
}public class ExpressShipment implements Shipment {// 实现快递物流
}public class InternationalShipment implements Shipment {// 实现国际物流
}

适用场景

  • 当不同实现有完全不同的行为时
  • 需要多重"泛化"时
  • 强调能力而非共同结构时

2.3 组合优于继承:现代DDD的实践

public class Product {private String id;private String name;private PricingStrategy pricingStrategy;public BigDecimal calculatePrice(OrderContext context) {return pricingStrategy.calculate(this, context);}
}public interface PricingStrategy {BigDecimal calculate(Product product, OrderContext context);
}public class StandardPricing implements PricingStrategy {// 标准定价策略
}public class DiscountPricing implements PricingStrategy {// 折扣定价策略
}public class BundlePricing implements PricingStrategy {// 捆绑定价策略
}

优势

  • 运行时可替换策略
  • 避免类爆炸
  • 更符合"组合优于继承"原则

2.4 特征值实现:马匹管理

  • 其中:所有马都有相同的属性和行为、唯一区别是颜色不同(枣红马、白马、黑马等)。
public class Horse {private String name;private int age;private HorseColor color;  // 类型区分字段public Horse(String name, int age, HorseColor color) {this.name = name;this.age = age;this.color = color;}public void run() { /* 奔跑实现 */ }public void eat() { /* 进食实现 */ }// 特定于颜色的行为(如果需要)public String getDescription() {return color.getDescription() + "马";}
}public enum HorseColor {RED("枣红"),WHITE("白"),BLACK("黑"),PALOMINO("金鬃"),DAPPLE("花斑");private final String description;HorseColor(String description) {this.description = description;}public String getDescription() {return description;}
}
public class HorseService {public void demo() {Horse redHorse = new Horse("赤兔", 5, HorseColor.RED);Horse whiteHorse = new Horse("的卢", 4, HorseColor.WHITE);System.out.println(redHorse.getDescription());   // 输出: 枣红马System.out.println(whiteHorse.getDescription()); // 输出: 白马// 所有马统一处理List<Horse> horses = List.of(redHorse, whiteHorse);horses.forEach(Horse::run);}
}

三 DDD中泛化的高级应用

3.1 限界上下文中的泛化

  • 在不同限界上下文中,同一概念的泛化可能不同。医院系统示例

患者管理上下文

Person
+String name
+String id
Patient
+MedicalRecord record
Staff
+Department department

预约系统上下文

AppointableEntity
+String id
+String name
+scheduleAppointment()
Doctor
+Specialty specialty
TreatmentRoom
+String roomNumber
  • 注意在不同上下文中,泛化层次和标准完全不同。

3.2 泛化的层次设计

银行账户系统示例

«abstract»
BankAccount
+String accountNumber
+BigDecimal balance
+deposit()
+withdraw()
+calculateInterest()
SavingsAccount
+BigDecimal interestRate
+calculateInterest()
CheckingAccount
+BigDecimal overdraftLimit
+withdraw()
FixedDepositAccount
+LocalDate maturityDate
+withdraw()

设计考量

  1. 抽象层次不宜过深(通常不超过3层)
  2. 每个层次都应增加明确的业务价值
  3. 避免过度设计,只为真正的业务差异创建子类

3.3 泛化与持久化的权衡

  • JPA继承策略示例
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "account_type")
public abstract class BankAccount {@Idprivate String accountNumber;private BigDecimal balance;// 公共字段
}@Entity
@DiscriminatorValue("SAVINGS")
public class SavingsAccount extends BankAccount {private BigDecimal interestRate;// 特有字段
}@Entity
@DiscriminatorValue("CHECKING")
public class CheckingAccount extends BankAccount {private BigDecimal overdraftLimit;// 特有字段
}

三种继承映射策略对比

策略类型数据库结构优点缺点
SINGLE_TABLE单表,用区分字段标识子类查询高效,无需连接字段稀疏,可能有NULL
JOINED父类子类分开表,主键关联结构规范,无冗余查询需连接,性能较差
TABLE_PER_CLASS每个具体类对应完整表查询具体类高效多态查询需UNION,设计复杂

四 避免泛化的常见陷阱

4.1 过度泛化:当抽象变成负担

反例

public abstract class Entity {private Long id;// 所有实体都需要的字段
}public abstract class Person extends Entity {private String name;private String email;// 所有人共有的字段
}public abstract class User extends Person {private String username;private String password;// 所有用户共有的字段
}public class Customer extends User {// 客户特有字段
}public class Employee extends User {// 员工特有字段
}public class SystemAdmin extends Employee {// 系统管理员特有字段
}

问题分析

  • 层次过深,修改基类影响范围大
  • 中间抽象类可能包含不相关功能
  • 实际业务需求可能不需要如此复杂的层次

4.2 错误泛化:混淆本质差异

错误案例

public abstract class PaymentMethod {private BigDecimal amount;public abstract void process();
}public class CreditCard extends PaymentMethod {// 信用卡支付
}public class Invoice extends PaymentMethod {// 发票支付
}public class BankTransfer extends PaymentMethod {// 银行转账
}public class Coupon extends PaymentMethod {// 优惠券
}

问题

  • 优惠券本质上不是支付方式,而是折扣机制
  • 强行泛化导致业务逻辑混乱
  • 更好的设计是将Coupon作为独立的折扣概念

4.3 泛化导致的数据污染

  • 问题示例
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Notification {@Idprivate Long id;private String recipient;private String content;private LocalDateTime sentTime;// 其他公共字段
}@Entity
public class EmailNotification extends Notification {private String emailSubject;private String ccList;
}@Entity
public class SMSNotification extends Notification {private String phoneNumber;
}@Entity
public class PushNotification extends Notification {private String deviceToken;private String appId;
}

问题

  • 单表策略下,表会包含所有子类的字段
  • 大多数记录会有大量NULL字段
  • 随着子类增加,表会变得臃肿

解决方案

  • 考虑使用JOINED策略
  • 或将完全不相关的通知类型拆分到不同限界上下文

五、实战案例:电商系统中的泛化设计

5.1 商品类别的泛化设计

  • 初始设计
Product
+String id
+String name
+BigDecimal price
PhysicalProduct
+BigDecimal weight
+Dimensions dimensions
DigitalProduct
+String downloadUrl
+BigDecimal fileSize
ServiceProduct
+Duration duration
+String provider

演进设计(考虑更多业务需求后):

«abstract»
Product
+String id
+String name
+calculatePrice()
InventoryFeatures
+StockLevel stock
+reserveStock()
ShippingFeatures
+BigDecimal weight
+Dimensions dimensions
+calculateShipping()
DigitalFeatures
+String downloadUrl
+String licenseKey
+generateDownload()
Book
+String isbn
+String author
Ebook
+String format
ConsultingService
+List<TimeSlot> availability
+scheduleSession()

设计演进说明

  1. 从简单的继承层次转变为更灵活的混合模式
  2. 使用接口(或mixin)表示横切关注点
  3. 将定价策略分离出来,符合单一职责原则
  4. 每个产品可以组合不同的特性

5.2 订单处理的状态模式实现

public class Order {private String orderId;private OrderState state;public void cancel() {state.cancel(this);}public void approve() {state.approve(this);}public void ship() {state.ship(this);}public void deliver() {state.deliver(this);}// 状态转移方法void changeState(OrderState newState) {this.state = newState;}
}public interface OrderState {void cancel(Order order);void approve(Order order);void ship(Order order);void deliver(Order order);
}public class DraftState implements OrderState {// 实现草案状态的行为
}public class ApprovedState implements OrderState {// 实现已批准状态的行为
}public class ShippedState implements OrderState {// 实现已发货状态的行为
}public class DeliveredState implements OrderState {// 实现已交付状态的行为
}public class CancelledState implements OrderState {// 实现已取消状态的行为
}

优势

  1. 每个状态的行为集中在一处
  2. 避免大量的if-else条件判断
  3. 新状态易于添加
  4. 状态转换逻辑明确

六 总结与最佳实践

6.1 何时使用泛化?

适合使用泛化的情况:

  • 存在明确的"是一种"业务关系
  • 多个概念有显著共享的行为和属性
  • 需要以统一接口处理不同概念时
  • 业务领域本身有明显的分类体系

应避免泛化的情况:

  • 仅为了代码复用而强行抽象
  • 差异大于共性的情况
  • 未来可能有根本性差异的概念
  • 技术驱动而非业务驱动的分类

6.3 泛化的替代方案

当泛化不合适时,考虑:

  1. 组合:将共性部分提取为独立组件
  2. 策略模式:将变化的行为抽象为策略
  3. 装饰器模式:动态添加功能
  4. 角色模式:允许对象动态获得能力

6.4 持续重构的视角

泛化设计应是演进式的:

  1. 开始时可以扁平化设计
  2. 随着重复代码的出现识别共性
  3. 当业务规则差异明确时引入抽象
  4. 定期审视泛化层次是否仍然合理
  • 记住Eric Evans的话:“深层次模型是逐步演进而非一次性设计出来的,它需要开发人员和领域专家持续协作,通过多次迭代精炼而成。”

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

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

相关文章

机箱流动空气热学仿真方案

机箱流动空气热学仿真方案(二维平面与三维) 一、物理模型与数学模型 1. 控制方程 流动与传热基本方程: 连续性方程:∇(ρu) = 0动量方程(Navier-Stokes):ρ(u∇)u = -∇p + μ∇u + F能量方程:ρcₚ(u∇)T = k∇T + Φ边界条件: 入口:速度入口(u=u₀, T=T₀)出口:压…

electron 如何配置 打开控制台

在 Electron 应用中&#xff0c;打开开发者工具&#xff08;即控制台&#xff09;通常有两种方式&#xff1a; 程序运行时手动打开 在 Electron 应用中&#xff0c;你可以通过编程方式打开开发者工具。这通常在你需要调试时非常有用。你可以在你的主进程&#xff08;通常是 ma…

MR7350用TTL刷机救砖过程

很久之前就买了一台Linksys的MR7350路由器&#xff0c;准备有OpenWRT的官方固件之后再拿它当轻NAS用&#xff0c;最近看到出了Snapshot版&#xff0c;于是就拿来刷机试试。经过我坚持不懈的折腾&#xff0c;终于把我的MR7350路由器刷成了砖&#xff0c;即便是通过开机过程中断电…

在NPU单算子(torch_npu )执行时如何进行性能优化?以MinerU为例

1 MinerU介绍 在AI技术快速发展的今天&#xff0c;大量非结构化数据的处理成为亟待解决的问题。尤其是PDF文档&#xff0c;作为最常见的文件格式之一&#xff0c;如何高效准确地提取其中的信息&#xff0c;成为了许多企业和研究机构的痛点。上海人工智能实验室&#xff08;上海…

鸿蒙OS开发IoT控制应用:从入门到实践

引言&#xff1a;万物互联时代的应用开发新范式 在物联网(IoT)技术迅猛发展的今天&#xff0c;智能设备数量呈指数级增长。据IDC预测&#xff0c;到2025年全球IoT连接设备数将达到416亿台。面对碎片化的IoT设备和多样化的控制需求&#xff0c;华为鸿蒙OS(HarmonyOS)应运而生&a…

五层网络模型:网络通信的核心框架

在网络通信的世界里&#xff0c;五层网络模型是一个基础而关键的概念。它帮助我们理解数据是如何在网络上从一个设备传输到另一个设备的。本文将详细介绍五层网络模型的每一层&#xff0c;以及它们在数据传输过程中的作用。 一、五层网络模型概述 五层网络模型是一种分层的网…

常见的强化学习算法分类及其特点

强化学习&#xff08;Reinforcement Learning, RL&#xff09;是一种机器学习方法&#xff0c;通过智能体&#xff08;Agent&#xff09;与环境&#xff08;Environment&#xff09;的交互来学习如何采取行动以最大化累积奖励。以下是一些常见的强化学习算法分类及其特点&#…

【LeetCode 热题 100】438. 找到字符串中所有字母异位词——(解法三)不定长滑动窗口+数组

Problem: 438. 找到字符串中所有字母异位词 题目&#xff1a;给定两个字符串 s 和 p&#xff0c;找到 s 中所有 p 的 异位词 的子串&#xff0c;返回这些子串的起始索引。不考虑答案输出的顺序。 【LeetCode 热题 100】438. 找到字符串中所有字母异位词——&#xff08;解法一&…

求区间最大值

题目描述 给定一个长度为 N 的数列&#xff0c;和 M 次询问&#xff0c;求出每一次询问的区间内数字的最大值。 输入描述 第一行包含两个整数 N,M&#xff0c;分别表示数列的长度和询问的个数。 第二行包含 N 个整数&#xff08;记为&#x1d44e;&#x1d456;&#xff09;&am…

调试HDMI音频能8通道播放声音

一、使用场景 我们是通过rk主控的hdmi接口播放音视频给到ite68051芯片解析出8声道数据,分别通过4路i2s的数据脚给给到fpga去解析 调试步骤: 1.根据相关手册配置hdmi输出,hdmi声卡注册,如下: hdmi0_sound: hdmi0-sound {status = "disabled";compatible = &qu…

PowerBI 柱状图显示MoM销量环比示例,以及解决相同列值时设置柱子颜色的问题

先看效果: 假设有Sales表: 1. 我们先给它新增一个计算列&#xff0c;显示销售日期的年月 销售日期YYYYMM YEAR(Sales[销售日期])*100 MONTH(Sales[销售日期]) 2. 然后新增一个计算表&#xff0c;用于保存当前最大的销售日期&#xff0c;和上一个月的日期 DateComparisonT…

【docker】构建时使用宿主机的代理

docker构建过程中报错: pip 下载失败 解决办法:传递宿主机的代理 把宿主机的 HTTP_PROXY/HTTPS_PROXY 传进去,导致容器内的 pip 依然连不上代理,下载 build-dependencies(比如 setuptools)就会失败。 下面两步即可解决: Docker 构建阶段,127.0.0.1:7890 指向的是 容…

[Java 基础]算法

什么是算法 程序 数据结构 算法 算法&#xff08;Algorithm&#xff09;就是解决问题的步骤&#xff0c;就像做菜的食谱一样&#xff0c;告诉计算机一步一步如何完成任务。 例如&#xff1a; 排序算法&#xff1a;把一堆数字从小到大排列搜索算法&#xff1a;在一堆数据里…

C++理解for循环 计算题三

计算a的值 #include <iostream> using namespace std; int main() { int a0;for(int i0;i<3;i){for(int j0;j<3;j){aij;}}cout<<"a的值是 "<<a<<endl; return 0; } 计算a的值 #include <iostream> using namespace std; int …

梳理React中的fiber架构

文章目录 产生背景核心概念工作原理工作流程优势特点 产生背景 在React16之前使用的虚拟DOM是数组的形式&#xff0c;又因为React本身是应用级框架&#xff0c;状态改变后并不能准确知道是哪个组件发生了改变&#xff0c;只能对整个应用进行diff协调&#xff0c;受限于虚拟DOM…

Modbus 数据模型:线圈、寄存器与功能码详解(二)

三、Modbus 功能码详解 3.1 功能码分类与作用 Modbus 功能码是 Modbus 通信协议中的关键组成部分&#xff0c;它如同一个 “指令指挥官”&#xff0c;在通信事务处理中扮演着核心角色。功能码占用 1 个字节的空间&#xff0c;取值范围为 1 到 255 &#xff08;0x01 - 0xFF&am…

多表连接查询:语法、注意事项与最佳实践

&#x1f517; 多表连接查询&#xff1a;语法、注意事项与最佳实践 多表连接是 SQL 的核心能力&#xff0c;用于关联多个表的数据。以下是深度解析&#xff0c;涵盖语法规范、性能陷阱及实战技巧&#xff1a; &#x1f4dc; 一、多表连接语法大全 1. 显式连接&#xff08;推荐…

使用Calibre对GDS进行数据遍历

在芯片的GDS数据里&#xff0c;使用Calibre对数据进行处理是非常常见的操作&#xff0c;但是GDS是一种和常规设计结构不太一样的一种数据&#xff0c;这里&#xff0c;通过这个小小的科普文章&#xff0c;一起看看怎么样在GDS里边做数据漫游吧&#xff01;闲言少叙&#xff0c;…

PyQtNode Editor 第二篇自定义可视化视图

在第一篇博客中,我们已经完成了 PyQtNode Editor 的基础环境搭建,并深入解析了自定义图形场景QDMGraphicsScene的实现原理。那个带有网格背景的场景就像一张空白的图纸,现在我们要在这张图纸上开始绘制真正的节点系统。 今天我们将聚焦于节点编辑器的核心数据结构设计,实现…

【扩欧应用】同余方程

与扩欧的联系 在同余方程的求解过程中&#xff0c;我们通常需要将方程转化为线性不定方程&#xff08;Diophantine 方程&#xff09;的形式&#xff0c;然后使用扩展欧几里得算法&#xff08;Extended Euclidean Algorithm, EEA&#xff09;求解。 同余方程是怎么转化为线性不…