DDD理论学习系列(11)-- 工厂
1.引言在针对大型的复杂领域进行建模时,聚合、实体和值对象之间的依赖关系可能会变得十分复杂。在某个对象中为了确保其依赖对象的有效实例被创建,需要深入了解对象实例化逻辑,我们可能需要加载其他相关对象,且可能为了保持其他对象的领域不变性增加了额外的业务逻辑,这样即打破了领域的单一责任原则(SRP),又增加了领域的复杂性。 那如何去创建复杂的领域对象呢?因为复杂的领域对象的生命周期可能需要协调才能进行创建。 这个时候,我们就可以引入创建类模式——工厂模式来帮忙,将对象的使用与创建分开,将对象的创建逻辑明确地封装到工厂对象中去。 2. DDD中的工厂我们有必要先理清工厂和工厂模式。 而针对工厂模式的实现主要有四种方式:
具体实现可以参考创建相似对象,就交给『工厂模式』吧。 3.封装内部结构当需要为聚合添加元素时,我们不能暴露聚合的结构。我们以添加商品到购物车为例,来讲解如何一步一步的使用工厂模式。 一般来说,添加到购物车需要几个步骤:
相关的应用层代码如下: namespace Application { public class AddProductToBasket { // ...... public void Add (Product product,Guid basketId) { var basket = _basketRepository.FindBy (basketId); var rate = TaxRateService.ObtainTaxRateFor (product.Id,country.Id); var item = new BasketItem (rate,product.Id,product.price); basket.Add (item); // ... } } } 在以上代码中,应用服务需要了解如何创建 namespace Application { public class AddProductToBasket { // ...... public void Add (Product product,Guid basketId) { var basket = _basketRepository.FindBy (basketId); basket.Add (product); // ... } } } namespace DomainModel { public class Basket { // ...... public void Add (Product product) { if (Contains (product)) GetItemFor (product).IncreaseItemQuantitBy (1); else { var rate = TaxRateService.ObtainTaxRateFor (product.Id,country.Id); var item = new BasketItem (rate,product.price); _items.Add (item); } } } } 以上代码展示了 然而,却引入了一个新的问题。为了根据商品创建有效的购物车子项,购物车需要提供一个有效的税率。为了创建这个税率,它要依赖一个 为了避免购物车承担额外的职责和隐藏购物车子项的内部结构。下面我们引入一个工厂对象来封装购物车子项的创建,包括获取正确的税率。 namespace DomainModel { public class Basket { // ...... public void Add (Product product) { if (Contains (product)) GetItemFor (product).IncreaseItemQuantitBy (1); else _items.Add (BasketItemFactory.CreateItemFor (product,deliveryAddress)); } } public class BasketItemFactory { public static void CreateBasketFrom (Product product,Country country) { var rate = TaxRateService.ObtainTaxRateFor (product.Id,country.Id); return new BasketItem (rate,product.price); } } } 引入工厂模式后,购物车的职责单一了,且隔离了来自购物车子项的变化,比如当税率变化时,或购物车子项需要其他信息创建时,都不会影响到购物车的相关逻辑。 4.隐藏创建逻辑
根据这个需求,我们可以抽象出一个 namespace DomainModel { public class Order { // ... public Delivery CreateFor (IEnumerable<Item> items,destination) { var kuaidi = KuaidiFactory.GetKuaidiFor (items,destination.Country); var delivery = new Delivery (items,destination,kuaidi); SetAsDispatched (items,delivery); return delivery; } } public class KuaidiFactory { public static Kuaidi GetKuaidiFor (IEnumerable<Item> deliveryItems,DeliveryAddress destination) { if (Shunfeng.CanDeliver (deliveryItems,destination)) { return new Shunfeng (deliveryItems,destination); } else { return new EMS (deliveryItems,destination); } } } } 如上代码所示,工厂类中我们封装了快递的选择逻辑。 当要创建的对象类型有多个选择,且客户端并不关心创建类型的选择时,我们可以在领域层使用工厂中去定义逻辑去决定要创建对象的类型。 5.聚合中的工厂方法提到工厂,并不是都需要需要创建独立的工厂类来负责对象的创建。一个工厂方法也可以存在于一个聚合中。
第一,这个动作是发生在购物车上的,所以我们可以毫不犹豫的在购物车中定义该行为。第二,将商品添加到愿望清单中去,就需要创建一个愿望清单子项。 namespace DomainModel { public class Basket { // ..... public WishListItem MoveToWishList (Product product) { //首先检查购物车中是否包含此商品 if (BasketContainsAnItemFor (product)) { //从购物车中获取该商品对应的子项 var basketItem = GetItemFor (product); //调用工厂方法根据购物车子项创建愿望清单子项 var wishListItem = WishListItemFactory.CreateFrom (basketItem); //从购物车中移除购物车子项 RemoveItemFor (basketItem); return wishListItem; } } } } 从上面可以看出 6.使用工厂重建对象在项目中,如果没有借助ORM进行数据模型与领域模型之间的映射,或者通过Web服务从一个老旧系统中获取领域对象,都需要我们对领域对象进行重建以满足领域的不变性。使用工厂来重建领域对象相对来说要比直接创建要复杂。
这个场景就属于购物车对象的重建,跟直接创建购物车对象就不同了。因为将订单中的所有子项恢复到购物车中去,我们就需要额外确保领域的不变性。比如订单子项对应的商品现在是否下架,如果下架我们是直接抛出异常,还是仍旧创建一个锁定的购物车子项,标记其为已下架状态? namespace DomainModel { public class Order { // ...... public Basket AddToCartFromOrder (Guid id) { OrderDTO rawData = ExternalService.ObtainOrder (id.ToString ()); var basket = BasketFactory.ReconstituteBasketFrom (rawData); return basket; } } namespace DomainModel { public class BasketFactory { // ... public static Basket ReconstituteBasketFrom (OrderDTO rawData) { Basket basket; // ... foreach (var orderItem in rawData.Items) { //是否下架 if (!ProductServie.IsOffTheShelf (orderItem.ProductId)) { var newBasketItem = newBasketItem (orderItem.ProductId,orderItem.Qty); basket.Add (newBasketItem); } else { throw new Exception ("订单中该商品已下架,无法重新购买!"); } } // ..... return basket; } } } 7.总结对象创建不是一个领域的关注点,但它确实存在于应用程序的领域层中。通过使用工厂可以有效的保证领域模型的干净整洁,以确保领域模型的对现实的准确表达。使用工厂具有以下好处:
然而,并不是任何需要实例化对象的地方都要使用工厂。只有当用工厂比使用构造函数更有表现力时,或存在多个构造函数容易造成混淆时,或者对要创建对象所依赖的对象不关心时,才选用工厂进行对象的创建。
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |