域应用程序对象通常是整个应用程序的中心,被很多子系统使用。它们表现了核心的数据和业务验证规则;因此,良好的域对象设计对于牢固的、高性能的和灵活的应用程序非常关键。
当我们开发那些使用了关系数据库的面向对象应用程序的时候,建立与数据库设计一致的域对象设计可以使应用程序更容易理解,这是因为在典型情况下,域对象表现了现实的"实体"和它们彼此之间的关系。因此,在很多情形下,域对象都被"映射"为关系数据库表和表间关系。但是,这种映射非常容易出错,从而以不合需要的域对象设计为终结。域对象的良好设计要求开发者对面向对象和关系的基本原理有深刻的理解。
域对象持续(Domain Objects Persistence)模式试图提供一种向关系数据库的映射关系,解除域对象与持续性逻辑之间的耦合关系。在这种模式中,域对象自身是不知道持续性机制的,因为其依赖关系是单方向的(从持续性对象到域对象)。这简化了域对象的设计,使它们更容易理解。它也向应用程序中的那些使用了域对象的子系统隐藏了持续性对象。更好的是,这种模式可以在分布式系统中使用,在这种情况下,只有域对象四处传递,使应用程序不用把持续机制暴露给外部代码。本文演示了如何使工厂(Factory)模式和域对象持续模式一起工作,来帮助域对象与持续性逻辑解除耦合。
定义问题
域对象是所有应用程序的中枢。它们捕获了数据库的核心数据模型和应用在数据上的业务规则。在典型情况下,应用程序的大多数子系统都依赖这些通用的域对象--这意味着域对象的映射越接近数据库大纲,应用程序开发者理解和使用它们就越容易,因为它们表现了数据库中的现实"实体"和"关系"。
如果域对象没有与应用程序的其它部分分开,你通常就得把持续性代码复制到很多个位置。同样,如果域对象没有与持续性代码分开,你遇到的情况就是,任何使用域对象的子系统都必须知道并依赖持续性对象。对持续性对象的任何更改都必然影响整个应用程序。因此,如果没有把域对象与应用程序和持续性代码分开都是不好的设计。
定义解决方案
实现上述目标的一个途径是把域对象分离到一个独立的子系统中,让应用程序的其它部分需要域数据的时候再使用它们。此外,你还必须把域对象与持续性代码分开。一方面,这种双重分离避免了代码重复;另一方面,它向域对象隐藏了持续性细节信息,建立了更容易修改的灵活设计。无论数据来自关系数据库、XML文件、平面文件、活动目录/LDAP或其它任何数据源,域对象和应用程序的其它部分都完全不会受到影响。
在分离持续性逻辑和域对象的过程中,你必须确保域对象没有依赖持续性代码。这样操作允许你把域对象暴露在那些你不希望暴露持续性代码的地方。
建立示例
下面的C#示例使用了Northwind示例数据库的Customer对象,它映射到数据库的Customer表。
public class Customer { // 私有数据成员 String _customerId; String _companyName; String _contactName; String _contactTitle; public Customer() {} // Customer对象的属性 public String CustomerId { get { return _customerId; } set { _customerId = value;} } public String CompanyName { get { return _companyName; } set { _companyName = value;} } public String ContactName { get { return _contactName; } set { _contactName = value;} } public String ContactTitle { get { return _contactTitle; } set { _contactTitle = value;} } } public interface ICustomerFactory { // 用于单行操作的标准事务方法 void Load(Customer cust); void Insert(Customer cust); void Update(Customer cust); void Delete(Customer cust); // 返回集合的查询方法 ArrayList FindCustomersByState(String state); } public class CustomerFactory : ICustomerFactory { //用于单行操作的标准事务方法 void Load(Customer cust) { /* Implement here */ } void Insert(Customer cust) { /* Implement here */ } void Update(Customer cust) { /* Implement here */ } void Delete(Customer cust) { /* Implement here */ } //返回集合的查询方法 ArrayList FindCustomersByState(String state) { /* 此处是实现代码 */ } } 下面的示例演示了客户端如何使用这段代码。 public class NorthwindApp { static void Main (string[] args) { Customer cust = new Customer(); CustomerFactory custFactory = new CustomerFactory(); //从Northwind数据库载入客户 cust.CustomerId = "ALFKI"; custFactory.load(cust); // 传递 Customer 对象 FooBar(cust); // custList是Customer对象列表 ArrayList custList = custFactory.FindCustomersByState("CA"); } }
在上面代码中,load方法根据CustomerID(应用程序可以把这个值传递到任何子系统中而不需要暴露持续性代码)从数据库中载入Customer对象。同样,如果你载入Customer对象的数组列表,你随后也可以传递数组列表,也没有持续性代码依赖。
使用域对象持续模式分离持续性代码和Customer对象,使得Customer对象更加面向对象,更易于理解,因为它的对象模型更加接近于数据库中的数据模型。此外,这种分离使你能够把Customer传递到应用程序的不同部分(甚至于通过.NET Remoting传递到分布式应用程序),而不需要暴露持续性代码。