Populates domain objects based on query results.
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 results. Each 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.
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.
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
...
}
Use this pattern when:
Consider these strategies when designing a domain object factory:
It is common in application code to execute SQL (or stored procedures) that return all available columns in the result set. In SQL this means using an asterisk (*) for the column list:
select * from MyTable
It is also common for a ConcreteDomainObjectFactory implementation to discard unneeded columns in the domain object since clients do not need them. This means that data is fetched from the database but is never used. Obviously, this situation should be optimized by returning data that is only needed for a particular ConcreteDomainObjectFactory implementation. Since a ConcreteDomainObjectFactory implementation is the primary consumer of the result set data, it makes sense for it to publish the names of the columns it requires. This optimization can be incorporated by enhancing IDomainObjectFactory interface with another method that provides this information. Generic data access code can then uses this information to refine the query to include only the columns that are needed to populate domain objects.
A generic implementation can provide the required column names by referencing metadata stored in configuration files or database tables. This approach is useful when you want to define new domain objects without writing additional mapping code.