1、引言
在针对复杂领域进行建模时,聚合、实体和值对象之间的依赖关系可能会变得十分复杂。在某个对象中为了确保其依赖对象的有效实例被创建,需要深入了解对象实例化逻辑,我们可能需要加载其他相关对象,且可能为了保持其他对象的领域不变性增加了额外的业务逻辑,这样即打破了领域的单一责任原则,又增加了领域的复杂性。
那如何去创建复杂的领域对象呢?因为复杂的领域对象的生命周期可能需要协调才能进行创建。 这个时候,我们就可以引入创建类模式——工厂模式来帮忙,将对象的使用与创建分开,将对象的创建逻辑明确地封装到工厂对象中去。
2、工厂模式
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
DDD中工厂的主要目标是隐藏对象的复杂创建逻辑;次要目标就是要清楚的表达对象实例化的意图。 而工厂模式是计模式中的创建类模式之一。借助工厂模式我们可以很好实现DDD中领域对象的创建。
而针对工厂模式的实现主要有四种方式:
- 简单工厂:简单实用,但违反开放封闭;
- 工厂方法:开放封闭,单一产品;
- 抽象工厂:开放封闭,多个产品;
- 反射工厂:可以最大限度的解耦。
这里就不多赘述了。
3、封装内部结构
当需要为聚合添加元素时,我们不能暴露聚合的结构。我们以添加商品到购物车为例,来讲解如何一步一步的使用工厂模式。
一般来说,添加到购物车需要几个步骤:
- 加载用户购物车
- 加载商品
- 创建新的购物车子项
相关的应用层代码如下:
// ......
public void add (Long goodsId, Long cartId) {
Cart cart = new Cart();
Goods goods = new Goods();
cart = cartService.findById(cartId);
goods = goodsService.findById(goodsId);
cart.getSeedCart().add(goods);
// ......
}
在以上代码中,应用服务需要了解如何创建Cart的详细逻辑。而这不应该时应用服务的职责,应用服务的职责在于协调。我们尝试做以下改变来避免暴露聚合的内部结构。
public class AddGoodsToCart {
// ......
public void add (Long goodsId, Long cartId) {
Cart cart = new Cart();
Goods goods = new Goods();
cart = cartService.findById(cartId);
goods = goodsService.findById(goodsId);
cart.Add(goods, cart);
}
}
public class Cart {
public void Add (Goods goods, Cart cart) {
cart.getSeedCart().add(goods);
cartService.savecart()
}
// ......
}
以上代码展示了Basket(购物车)对象提供一个Add方法,用来完成添加商品到购物车的业务逻辑,对应用服务隐藏了购物车如何存储商品的细节。另外购物车聚合能够确保其内部集合的完整性,因为它可以确保领域的不变性。通过这种方式,完成了职责的切换,现在的应用服务要简单的多。 变更 然而,却引入了一个新的问题。根据需求,它需求要依赖一个Special(特别需求)。获取创建购物车子项依赖的税率,这并不属于购物车的职责。而按照上面的实现,购物车承担了第二责任,因为它必须始终了解如何创建有效的购物车子项以及在哪里去获取有效的税率。
为了避免购物车承担额外的职责和隐藏购物车子项的内部结构。下面我们引入一个工厂对象来封装购物车子项的创建,包括获取Special。
public class Cart {
public void Add (Goods goods, Cart cart) {
CartFactory(cart, goods);
}
// ......
}
public class CartFactory {
public static void CreateCartFrom (Cart cart, Goods goods) {
Special special = specialService.ObtainTaxRateFor (cart.getId());
return new Cart (special, product.Id, goods);
}
}
引入工厂模式后,购物车的职责单一了,且隔离了来自购物车子项的变化,比如个需求变化时,或购物车子项需要其他信息创建时,都不会影响到购物车的相关逻辑。
4、隐藏创建逻辑
考虑这样的需求:订单创建成功后,进行发货处理时,要求根据订单的商品和收件人信息选择合适的快递方式。比如默认发顺丰,顺丰无法送达的选择中国邮政。
根据这个需求,我们可以抽象出一个Kuaidi(快递)对象用来封装快递信息,和一个Delivery(发货)对象用来封装发货信息(货物、收件人信息、快递等)。创建Delivery的职责我们可以放到Order中去,但针对Order来说它并不知道要创建(选择)哪一种Kuaidi(快递)。所以,我们可以创建一个KuaidiFactory工厂负责Kuaidi对象的创建。
当要创建的对象类型有多个选择,且客户端并不关心创建类型的选择时,我们可以在领域层使用工厂中去定义逻辑去决定要创建对象的类型。
5、小结
对象创建不是一个领域的关注点,但它确实存在于应用程序的领域层中。通过使用工厂可以有效的保证领域模型的干净整洁,以确保领域模型的对现实的准确表达。使用工厂具有以下好处:
- 工厂将领域对象的使用和创建分离。
- 通过使用工厂类,可以隐藏创建复杂领域对象的业务逻辑。
- 工厂类可以根据调用者的需要,创建相应的领域对象。
然而,并不是任何需要实例化对象的地方都要使用工厂。只有当用工厂比使用构造函数更有表现力时,或存在多个构造函数容易造成混淆时,或者对要创建对象所依赖的对象不关心时,才选用工厂进行对象的创建。
DDD工厂并无特殊的地方,与经典工厂设计是一致的。