Friday 29 June 2012

What is Abstract Factory Design Patterns?

Using references to interfaces instead of references to concrete classes is an important way of minimizing ripple effects. The user of an interface reference is always protected from changes to the underlying implementation.
The Abstract Factory pattern is one example of this technique. Users of an Abstract Factory can create families of related objects without any knowledge of their concrete classes. (A typical business application would usually not need to use this technique - it is more suitable for toolkits or libraries.)

Example
An Abstract Factory is a major part of the full Data Access Object scheme. Here, the idea is to allow the business layer to interact with the data layer almost entirely through interface references. The business layer remains ignorant of the concrete classes which implement the datastore.
There are two distinct families of items here :
  • the various datastore implementations (MySql, FileScheme)
  • the various business objects which need persistence (UserDevice, etc.)
This corresponds to the two operations which must be done to return a persisted object. The type of datastore is first determined (an implementation of DAOFactory is returned), using a Factory Method.
(This example would be much improved by not having imports of ServletConfig all over the place.) 
package myapp.data;

import javax.servlet.ServletConfig;

/**
* Allows selection of a DAOFactory, without the user being
* aware of what choices are available.
*
* This style allows the data layer to make the decision regarding what
* DAOFactory is to be used by the business layer.
*/
public final class DatastoreSelector {

  /**
  * @param aConfig is non-null.
  */
  public static DAOFactory getDAOFactory( ServletConfig aConfig ){
    //demonstrate two implementation styles :
    return stringMappingImpl( aConfig );
    //return classNameImpl( aConfig );
  }

  // PRIVATE //

  /**
  * Use an ad hoc String mapping scheme, and introduce an if-else
  * branch for each alternative.
  */
  private static DAOFactory stringMappingImpl( ServletConfig aConfig ){
    if ( aConfig == null ) {
      throw new IllegalArgumentException("ServletConfig must not be null.");
    }
    //examine the config to extract the db identifier
    final String storageMechanism = aConfig.getInitParameter("DatastoreName");
    if ( storageMechanism.equals("MySql")) {
      return new DAOFactoryMySql( aConfig );
    }
    else if ( storageMechanism.equals("FileScheme") ) {
      return new DAOFactoryFileScheme( aConfig );
    }
    else {
      throw new IllegalArgumentException("Unknown datastore identifier.");
    }
  }

  /**
  * Make direct use of the class name, and use reflection to create the
  * object.
  */
  private static DAOFactory classNameImpl( ServletConfig aConfig ){
    DAOFactory result = null;
    //examine the config to extract the class name
    final String storageClassName = aConfig.getInitParameter("DatastoreClassName");
    try {
      Class storageClass = Class.forName(storageClassName);
      //Class.newInstance can be used only if there is a no-arg constructor ;
      //otherwise, use Class.getConstructor and Constructor.newInstance.
      Class[] types = { javax.servlet.ServletConfig.class };
      java.lang.reflect.Constructor constructor = storageClass.getConstructor(types);
      Object[] params = { aConfig };
      result = (DAOFactory) constructor.newInstance( params );
    }
    catch (Exception ex){
      System.err.println("Cannot create DAOFactory using name: " + storageClassName);
      ex.printStackTrace();
    }
    return result;
  }
} 

package myapp.data;

/**
* Returns an implementation of all XXXDAO interfaces.
*/
public interface DAOFactory {

  /**
  * Returns an implementation of DeviceDAO, specific to a
  * particular datastore.
  */
  DeviceDAO getDeviceDAO() throws DataAccessException;

  /**
  * Returns an implementation of UserDAO, specific to a
  * particular datastore.
  */
  UserDAO getUserDAO() throws DataAccessException;
} 


Then, each DAOFactory implementation can return its implementations of the XXXDAO interfaces (DeviceDAOUserDAO), which are the concrete worker classes which implement persistence. 
package myapp.data;

import javax.servlet.ServletConfig;

/**
* Package-private implementation of DAOFactory.
* This is for a MySql database.
*/
final class DAOFactoryMySql implements DAOFactory {

  DAOFactoryMySql( ServletConfig aServletConfig ){
    if ( aServletConfig == null ) {
      throw new IllegalArgumentException("ServletConfig must not be null.");
    }
    fConfig = aServletConfig;
  }

  public UserDAO getUserDAO() throws DataAccessException {
    return new UserDAOMySql(fConfig);
  }

  public DeviceDAO getDeviceDAO() throws DataAccessException {
    return new DeviceDAOMySql(fConfig);
  }

  /// PRIVATE ////
  private final ServletConfig fConfig;
} 

package myapp.data;

import javax.servlet.ServletConfig;

/**
* Package-private implementation of DAOFactory.
* This is for an ad hoc file scheme.
*/
final class DAOFactoryFileScheme implements DAOFactory {

  DAOFactoryFileScheme( ServletConfig aServletConfig ){
    if ( aServletConfig == null ) {
      throw new IllegalArgumentException("ServletConfig must not be null.");
    }
    fConfig = aServletConfig;
  }

  public UserDAO getUserDAO() throws DataAccessException {
    return new UserDAOFileScheme(fConfig);
  }

  public DeviceDAO getDeviceDAO() throws DataAccessException {
    return new DeviceDAOFileScheme(fConfig);
  }

  /// PRIVATE ////
  private final ServletConfig fConfig;
} 


Here is an example of a XXXDAO interface, and a toy implementation for a MySql database. 
package myapp.data;

import myapp.business.Device;

/**
* The business layer talks to the data layer about storage of Device objects
* through a DeviceDAO reference.
*
* DataAccessException is a wrapper class, which exists only to wrap
* low-level exceptions specific to each storage mechanism (for example,
* SQLException and IOException). When an implementation class throws
* an exception, it is caught, wrapped in a DataAccessException, and then
* rethrown. This protects the business layer from ripple effects caused by
* changes to the datastore implementation.
*/
public interface DeviceDAO {
  Device fetch( String aId ) throws DataAccessException;
  void add( Device aDevice ) throws DataAccessException;
  void change( Device aDevice ) throws DataAccessException;
  void delete( Device aDevice ) throws DataAccessException;
}
 

package myapp.data;

import myapp.business.Device;
import java.net.InetAddress;
import javax.servlet.ServletConfig;

/**
* An implementation of DeviceDAO which is specific to a MySql database.
*
* This class must be package-private, to ensure that the business layer
* remains unaware of its existence.
*
* Any or all of these methods can be declared as synchronized. It all depends
* on the details of your implementation.
*
* Note that it is often possible to use properties files (or ResourceBundles) to
* keep SQL out of compiled code, which is often advantageous.
*/
final class DeviceDAOMySql implements DeviceDAO {

  DeviceDAOMySql( ServletConfig aConfig ) {
    //..elided
  }

  public Device fetch( String aId ) throws DataAccessException {
    //create a SELECT using aId, fetch a ResultSet, and parse it into a Device
    return null; //toy implementation
  }

  synchronized public void  add( Device aDevice ) throws DataAccessException{
    //parse aDevice into its elements, create an INSERT statement
  }

  synchronized public void change( Device aDevice )  throws DataAccessException{
    //parse aDevice into its elements, create an UPDATE statement
  }

  synchronized public void delete( Device aDevice )  throws DataAccessException {
    //extract the Id from aDevice, create a DELETE statement
  }
} 


It is important to note most of the data layer's concrete classes are package-private - only DatastoreSelector and DataAccessException are public. 

0 comments:

Post a Comment

Site Search