Domain Object Factory

Summary

Purpose

Populates domain objects based on query results.

Scenario

In the Selection Factory pattern we mentioned that a primary function for any domain object mapping is  to identify the set of relational data on which to operate. Applications indicate this set using identity objects and the relational concept that is analogous to an identity object is the WHERE clause in SQL statements. Another primary function for any domain object mapping is populating domain objects based on query resultsEach row of a query result contains data needed to create and initialize a new domain object. In some cases, this is as simple as copying data directly from the result set to the new domain object. However, it is also common to include additional mappings and translations.

Consider a database table that contains customer names and addresses. Sprinkling this table's physical details throughout the application is detrimental to the application's maintainability and severely restricts future changes to the table and its supporting database. Defining a Customer domain object with which the application can interact helps to encapsulate the physical table details in a single object. Domain object mapping is responsible for translating a row from a given table to an object that exposes the row's columns as properties. This situation is shown below:

The Domain Object Factory pattern therefore, describes a strategy for encapsulating the details of this type of translation. Since these details are likely to vary among different tables and domain objects, you can define factory objects for each translation variation. The input to a Domain Object Factory is a row of data and the output is a corresponding domain object.

Structure & UML

The following figure illustrates the static structure for the Domain Object Factory :

The Domain Object Factory defines a single operation for creating and populating new domain objects based on single rows of data. You can tailor the format of the input data based on the physical data representation that is most convenient for your middleware code. For example, in ADO.NET, NewDomaonObject will most likely take a DataRow object as its input parameter. Better yet, if you are using the Layers pattern and the domain object mapping sits on top of a Data Accessor, you can define NewDomainObject to accept a more abstract data representation that is independent of any database platform.

ConcreteDomainObjectFactory implements IDomainObjectFactory for a specific database entity or domain object type. The overall domain object mapping is likely to require multiple ConcreteDomainObjectFactory implementations with customized mapping details encapsulated within each of them. Client code and generic domain object mapping infrastructure code refer only to IDomainObjectFactory interface, keeping it decoupled from any specific mapping details.

The following figure illustrates the sequence diagram for the when a client requests a new domain object:

The client issues a database read and passes the results to the appropriate ConcreteDomainObjectFactory.NewDomainObject method. NewDomainObject returns a domain object that the client can use for application logic.

Example

The following example illustrates the Domain Object Factory pattern for the Customer scenario presented earlier:

public interface IDomainObjectFactory
{
    ArrayList GetColumnNames();
    object NewDomainObject( DataRow row );
}

// Domain objects
public class Customer
{
    // Data members
    private long m_lCustID;
    private string m_strLastName = "";
    private string m_strFirstName = "";
    private string m_strAddress= "";

    // Properties
   
public long ID
    {
        get { return m_lCustID; }
        set { m_lCustID = value; }
    }

    public string LastName
    {
        get { return m_strLastName; }
        set { m_strLastName = value; }
    }

    public string FirtName
    {
        get { return m_strFirstName; }
        set { m_strFirstName = value; }
    }

    public string Address
    {
        get { return m_strAddress; }
        set { m_strAddress = value; }
    }
}

public class CustomerFactory : IDomainObjectFactory
{
    // IDomainObjectFactory implementation
    public ArrayList GetColumnNames()
    {
        ArrayList al = new ArrayList( new string[] {"ID","Last","First","Address" } );
        return al;
    }

    public object NewDomainObject( DataRow row )
    {
        Customer obCust = new Customer();
        obCust.ID = (long)row["ID"];
        obCust.LastName = (string)row["Last"];
        obCust.FirtName = (string)row["First"];
        obCust.Address = (string)row["Address"];

        return obCust;
    }
}

// Client code
public void Initialize()
{
    // Initialize a customer factory
    IDomainObjectFactory custFactory = new CustomerFactory();

    // Get columns names that the domain object factory requires 
    ArrayList alColNames = custFactory.GetColumnNames();

    // Use alColNames to build a SQL statemenet to select those columns
    // requires by the factory and then issue a query to the database
    DataTable dtCustomers = new DataTable();
    ...

    // Assuming that data has been obtained, get a customer domain object
    // for each row from the returned data
    ArrayList alCustomers = new ArrayList();
    foreach (DataRow row in dtCustomers.Rows)
    {
        Customer obCust = (Customer)custFactory.NewDomainObject( row );
        alCustomers.Add( obCust );
    }

    // Now we have an array list of Customer domain objects. Application can process
    // them as required 
    ...
}

Applicability

Use this pattern when:

Strategies / Variants

Consider these strategies when designing a domain object factory:

Benefits

Related Patterns