Factory method pattern in java using generics, how

2020-05-28 21:15发布

问题:

I have code that looks like follows:

public interface BaseDAO{
// marker interface
}

public interface CustomerDAO extends BaseDAO{
public void createCustomer();
public void deleteCustomer();
public Customer getCustomer(int id);
// etc
}

public abstract class DAOFactory {
public BaseDAO getCustomerDAO();
public static DAOFactory getInstance(){
  if(system.getProperty("allowtest").equals("yes")) {
  return new TestDAOFactory();
  }
  else return new ProdDAOFactory();
}

public class TestDAOFactory extends DAOFactory{
public BaseDAO getCustomerDAO() {
  return new TestCustomerDAO(); // this is a concrete implementation
  //that extends CustomerDAO
  //and this implementation has dummy code on methods
}

public class ProdDAOFactory extends DAOFactory {
public BaseDAO getCustomerDAO() {
  return new ProdCustomerDAO(); // this implementation would have 
  // code that would connect to the database and do some stuff..
}
}

Now, I do know that this code smells.. for many reasons. However, this code is here too: http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html, refer 9.8

What I intend to do is this: 1) Switch my DAOs implementations at runtime based on environment (system properties). 2) Make use of java generics so that I can avoid type casting... for instance does something like this:

CustomerDAO dao = factory.getCustomerDAO();
dao.getCustomer();

As opposed to:

CustomerDAO dao = (CustomerDAO) factory.getCustomerDAO();
dao.getCustomer();

Your thoughts and suggestions, please.

回答1:

You should define the factory like that:

public abstract class DAOFactory<DAO extends BaseDAO> {
public DAO getCustomerDAO();
public static <DAO extends BaseDAO> DAOFactory<DAO> getInstance(Class<DAO> typeToken){
  // instantiate the the proper factory by using the typeToken.
  if(system.getProperty("allowtest").equals("yes")) {
  return new TestDAOFactory();
  }
  else return new ProdDAOFactory();
}

getInstance should return a proper typed DAOFactory.

The factory variable will have the type:

DAOFactory<CustomerDAO> factory = DAOFactory<CustomerDAO>.getInstance(CustomerDAO.class);

and the usage will be properly typed:

CustomerDAO dao = factory.getCustomerDAO();
dao.getCustomer();

the only problem will probably be a cast required inside the getInstance methods.



回答2:

There's a bunch of articles detailing what you need:

  • Generic DAO Pattern With JDK 5.0
  • Generic Data Access Objects (same as above)
  • Don't repeat the DAO (with Spring, but the principles are the same)
  • Java 1.5 Generic DAO (Spring again)

Please note that, unlike your example, there is no reason why the methods of DAOFactory should not return the actual subclasses (i.e. CustomerDAO getCustomerDAO()). Furthermore, main benefit of using generic DAOs is having the entity type "genericized", so you don't have to cast from load()/get()/find() and similar methods.



回答3:

Your example doesn't demonstrate a need for BaseDAO, and there is no reason why DAOFactory.getCustomerDAO() shouldn't be declared to return a CustomerDAO. So, I don't really see a need for generics there. However, consider the following:

interface DataAccess<T> {
  void store(T entity);
  T lookup(Serialiable identifier);
  void delete(Serializable identifier);
  Collection<? extends T> find(Criteria query);
}

abstract class DataAccessFactory {
  abstract DataAccess<T> getDataAccess(Class<T> clz);
  static DataAccessFactory getInstance() {
    ...
  }
}

I've used something like this approach in several projects, and it is very nice to write one DAO that works for every entity in the model. The weakness is the "finder" methods. There are some neat approaches, and upcoming work in JPA is standardizing a "Criteria" API, but for now it is often easiest to expose the underlying persistence mechanism's criteria.



回答4:

When I've used factories I've typically used instanceof to determine the true type of the object. For example:

CustomerDAO dao;
if (factory.getCustomerDAO() instanceof CustomerDAO) {
   dao = factory.getCustomerDAO();
}
dao.getCustomer();

This just seems cleaner to me, especially if factory.getCustomerDAO() doesn't return anything close to a CustomerDAO (due to changes in implementation).

Just my two cents.