了解领域对象如何帮助建模和实现业务逻辑,提高软件设计的质量和可维护性。
chou403
/ DDD
/ c:
/ u:
/ 8 min read
一学一个不吱声
在领域驱动设计(Domain-Driven Design, DDD)中,领域对象是领域模型中的核心概念,它代表系统在业务逻辑中的实体或数据,并且封装了与业务相关的行为。领域对象不仅仅是数据结构,它应该具备清晰的业务语义,负责处理业务规则和状态变化,避免业务逻辑分散在服务层或应用层中。
为了更好地理解领域对象,让我们从几个关键方面深入探讨:
1. 领域模型的基本概念
-
实体(Entity): 实体是指在领域中具有唯一标识的对象,通常是系统中的重要业务对象。它不仅仅有数据属性,还包括行为和业务规则。
示例: 在电商系统中,
Order
(订单)就是一个实体。它有一个唯一的标识符(订单ID),订单的状态(已创建,已发货,已取消等),以及业务逻辑,比如修改订单状态,取消订单等。 -
值对象(Value Object): 值对象没有唯一标识,通常表示某些属性的组合。它们是不可变的,主要用于封装属性,而不是行为。
示例: 在一个订单中,
Address
(地址)可以是一个值对象,它表示订单的收货地址,包括省市,街道,邮编等信息。地址的变化不会影响订单的唯一标识。 -
聚合(Aggregate): 聚合是一个领域对象的集合,它们被当作一个单元一起修改。聚合有一个根实体(称为聚合根),只有通过这个聚合根才能访问聚合中的其他对象。
示例: 在订单系统中,
Order
可能是一个聚合根,而OrderItem
(订单项)是聚合的一部分。外部代码只能通过Order
来操作订单项,确保业务一致性。 -
工厂(Factory): 工厂模式用于创建复杂的领域对象或聚合。通常领域对象的构造比较复杂,因此使用工厂方法来封装构造过程。
-
仓储(Repository): 仓储用于管理领域对象的持久化。它提供了从数据库中查找和存储实体的方式,但隐藏了数据访问的具体实现细节。
2. 充血模型 vs 贫血模型
领域对象的关键在于它不仅仅是数据的载体,它还应包含与业务逻辑相关的行为。这就是所谓的充血模型(Rich Domain Model)与贫血模型(Anemic Domain Model)的区别:
-
充血模型: 在充血模型中,领域对象不仅包含属性,还包含与这些属性相关的业务逻辑。对象是行为与状态的结合体,业务逻辑封装在领域对象中。这符合 DDD 的思想,强调通过领域对象来表达业务行为。
示例:
class Order { private status: string; constructor() { this.status = "Created"; } cancelOrder() { if (this.status !== "Shipped") { this.status = "Cancelled"; } else { throw new Error("Order cannot be cancelled once shipped"); } } }
在上面的代码中,
Order
对象不仅存储了订单状态,还包含了取消订单的业务逻辑。 -
贫血模型: 在贫血模型中,领域对象只是数据的容器,业务逻辑全部放在服务层。这种方式可能违背了 DDD 的核心思想,因为业务逻辑脱离了数据,导致代码的组织不清晰。
示例:
class Order { status: string; // 仅包含数据,没有行为 } class OrderService { cancelOrder(order: Order) { if (order.status !== "Shipped") { order.status = "Cancelled"; } else { throw new Error("Order cannot be cancelled once shipped"); } } }
在贫血模型中,
Order
仅包含数据,所有的业务逻辑都在OrderService
中,这种设计可能导致业务逻辑散乱,不利于维护。
3. 领域对象的职责
领域对象的核心职责是封装业务规则和状态管理,因此它不仅仅是数据库表的映射,而是业务的忠实表达。通过领域对象来处理业务逻辑,可以确保业务规则的一致性和完整性。
领域对象的设计要点:
-
行为优于数据: 领域对象不仅持有数据,更应该包含与数据相关的行为,避免将业务逻辑放到应用层或服务层。
-
领域对象的边界: 每个领域对象应该专注于它的职责,不要跨越领域边界。例如,订单的状态管理应该由
Order
处理,而库存的管理应该由Inventory
处理。 -
保持不变性: 值对象(Value Objects)在领域中通常是不可变的,它们的修改需要创建新的对象,这样可以确保状态的一致性和正确性。
4. 领域对象示例
以下是一个电商系统中的 Order
类,它不仅封装了订单的属性,还包含了核心的业务逻辑,如添加商品,修改订单状态等。
class Order {
private id: string;
private status: string;
private items: OrderItem[];
constructor(id: string) {
this.id = id;
this.status = "Created";
this.items = [];
}
addItem(item: OrderItem) {
if (this.status !== "Created") {
throw new Error("Cannot add items to a non-created order");
}
this.items.push(item);
}
shipOrder() {
if (this.items.length === 0) {
throw new Error("Cannot ship an order with no items");
}
this.status = "Shipped";
}
cancelOrder() {
if (this.status === "Shipped") {
throw new Error("Shipped orders cannot be cancelled");
}
this.status = "Cancelled";
}
}
class OrderItem {
constructor(
public productId: string,
public quantity: number,
) {}
}
在这个例子中,Order
封装了订单的状态及其行为(添加商品,发货,取消订单等),而这些逻辑是订单业务的核心。在 DDD 的设计中,领域对象负责管理和保护这些核心业务逻辑。
5. 总结
领域对象是 DDD 中的核心概念,它不仅仅是数据结构,更重要的是它应该封装与业务相关的逻辑。通过领域对象,可以确保业务规则的一致性,可维护性,并通过对象的行为来表达业务语义。
- 充血模型是 DDD 强调的设计方式,通过领域对象封装业务逻辑。
- 贫血模型虽然简单,但将业务逻辑放到了服务层,导致业务代码分散,容易出现问题。
DDD 的核心是将业务逻辑和数据紧密结合在领域对象中,使得系统的设计更加贴合业务。