动机 :
在设计面向对象应用程序架构的时候,
对象会包含相关的企业逻辑,而不是单纯的数据对象。
但是当企业逻辑需要取得其他对象一起运算,如何「取得」是一件很复杂的事情。
例如说:
在系统内有一个「查询客户订单总金额」的企业逻辑,需要从系统取出客户的所有订单做金额加总。
这个企业逻辑实作上可以分配到不同的对象,这边我们先定义这个企业逻辑是客户对象的职责。
并用下列的程序代码,实作这个企业逻辑,
这样的范例是可以正常的工作。
但是换个场景会发现,在只是要编辑客户电话的时候,也需要取得订单查询接口。
当系统越来越庞大,企业逻辑越来越多时,这个范例架构就会显得是个灾难。
而且再细看的话会发现订单有参考到客户,这个范例有循环相依的问题。
namespace ConsoleApplication001 { public class Customer { public Guid Id { get; private set; } public string Name { get; set; } private readonly IOrderRepository _orderRepository = null; public Customer(Guid id, IOrderRepository orderRepository) { this.Id = id; this.Name = string.Empty; _orderRepository = orderRepository; } public int GetTotal() { int total = 0; foreach (Order order in _orderRepository.GetListByCustomer(this)) { total += order.Price; } return total; } } public class Order { public Guid Id { get; private set; } public Customer Customer { get; private set; } public int Price { get; set; } public Order(Guid id, Customer customer) { this.Id = id; this.Customer = customer; this.Price = 0; } } public interface IOrderRepository { IEnumerable<Order> GetListByCustomer(Customer customer); } }
将系统重写成下列的程序代码,改由运作时将订单查询接口注入。
这样的范例也是可以正常的工作,但是依然没有解决循环相依的问题。
namespace ConsoleApplication002 { public class Customer { public Guid Id { get; private set; } public string Name { get; set; } public Customer(Guid id) { this.Id = id; this.Name = string.Empty; } public int GetTotal(IOrderRepository orderRepository) { int total = 0; foreach (Order order in orderRepository.GetListByCustomer(this)) { total += order.Price; } return total; } } public class Order { public Guid Id { get; private set; } public Customer Customer { get; private set; } public int Price { get; set; } public Order(Guid id, Customer customer) { this.Id = id; this.Customer = customer; this.Price = 0; } } public interface IOrderRepository { IEnumerable<Order> GetListByCustomer(Customer customer); } }
本文介绍一个『Lazy Decoration模式』。
定义对象的职责跟规则,将对象与对象之间的相依性做切割。
用来解决上列描述的问题。
结构 :
下图是这个架构的示意图。
可以看到除了系统原本就有的客户、订单、订单查询接口之外,多了两个客户实体、客户实体工厂对象。
订单到客户之间的相依,透过客户实体、客户实体工厂做了相依性切割。
并且将「查询客户订单总金额」的企业逻辑,改分派到(客户实体)上。
需要做「查询客户订单总金额」时,再建立(客户实体)来查询。
而(客户实体)因为是继承自(客户)对象,在后续的应用,也可以直接将它当作(客户)来用。
实作 :
文字写起来很复杂,其实看程序代码很简单。
首先定义基本的(客户)、(订单)、(订单查询接口)这三个对象。
要特别注意的是(客户)对象,它除了基本的建构函式之外,还包含了一个将自己当作参数的建构函式。
这让继承的对象,不用关注属性增加、属性更名、属性值初始化...等等工作。
namespace ConsoleApplication003 { public class Customer { public Guid Id { get; private set; } public string Name { get; set; } public Customer(Guid id) { this.Id = id; this.Name = string.Empty; } public Customer(Customer item) { this.Id = item.Id; this.Name = item.Name; } } public class Order { public Guid Id { get; private set; } public Customer Customer { get; private set; } public int Price { get; set; } public Order(Guid id, Customer customer) { this.Id = id; this.Customer = customer; this.Price = 0; } } public interface IOrderRepository { IEnumerable<Order> GetListByCustomer(Customer customer); } }
再来看看(客户实体)对象,
它继承了(客户)对象,并且实作了「查询客户订单总金额」这个企业逻辑。
namespace ConsoleApplication003 { public class CustomerEntity : Customer { private readonly IOrderRepository _orderRepository = null; public CustomerEntity(Customer item, IOrderRepository orderRepository) : base(item) { _orderRepository = orderRepository; } public int GetTotal() { int total = 0; foreach (Order order in _orderRepository.GetListByCustomer(this)) { total += order.Price; } return total; } } }
最后是(客户实体工厂),
它很简单的只是在建立(客户实体)时,将(订单查询接口)对象做注入的动作。
namespace ConsoleApplication003 { public class CustomerEntityFactory { private readonly IOrderRepository _orderRepository = null; public CustomerEntityFactory(IOrderRepository orderRepository) { _orderRepository = orderRepository; } public CustomerEntity Create(Customer item) { return new CustomerEntity(item, _orderRepository); } } }
在这些对象整个建立完毕之后,
当我们要做客户数据的新增、修改、删除、查询,直接将(客户)对象进出 Data Access Layer(DAL)。
namespace ConsoleApplication003 { class Test001 { static void MainXXX(string[] args) { ICustomerRepository customerRepository = null; // 使用例如Spring.Net、Provider Pattern来反射生成。 foreach (Customer customer in customerRepository.GetAll()) { Console.WriteLine(customer.Name); } } } } namespace ConsoleApplication003 { public interface ICustomerRepository // Customer的DAL界面 { Customer[] GetAll(); Customer GetById(Guid id); } }
当要查询某个客户的订单总金额时,建立(客户实体)就可以做查询。
namespace ConsoleApplication003 { class Test002 { static void MainXXX(string[] args) { ICustomerRepository customerRepository = null; // 使用例如Spring.Net、Provider Pattern来反射生成。 IOrderRepository orderRepository = null;// 使用例如Spring.Net、Provider Pattern来反射生成。 CustomerEntityFactory customerEntityFactory = new CustomerEntityFactory(orderRepository); Customer customer = customerRepository.GetById(Guid.Parse("xxxxx")); CustomerEntity customerEntity = customerEntityFactory.Create(customer); Console.WriteLine(customerEntity.GetTotal()); } } } namespace ConsoleApplication003 { public interface ICustomerRepository // Customer的DAL界面 { Customer[] GetAll(); Customer GetById(Guid id); } }
后记 :
这个模式除了范例示范的企业逻辑分派为对象方法之外,也可以延伸成为对象属性、对象事件等等的功能。
在实作的时候这个模式,也能将不同的企业逻辑做分类。例如 : CustomerQueryEntity、CustomerVerifyEntity。
最后一提的是,这个模式是从 [Application Architecture] : Lazy Boundary 模式 所重整提取出来。
当我们,
将在开发软件项目的时候,遇到的各种不同功能面对象,归类并取一个好记的名字。
反复重整功能面对象跟名词,最终就会产生属于自己的模式。 :D