Java – Generic factories

An example of using generics to tie together the return type of a Java factory method with its parameters.

Generics start for most Java developers as a means of providing type safety and eliminating casting when using collections, turning code like this:-

       List rawList = new ArrayList();
       rawList.add("A string");
       String aString = (String)rawList.get(0);

Into code like this:-

       List<String> genericList = new ArrayList<String>();
       genericList.add("A string");
       String aString = genericList.get(0);

Unfortunately, for many Java developers, that’s also where generics finish, probably through a combination of the reduced profit from their other applications and the slightly Byzantine syntax you need to master to get it.

Object factories

Programming to an interface rather than an implementation and the use of factory methods to centralise the creation of implementations are basic good practice to be seen throughout many a code base. So, quite often, are rather brittle and verbose factory implementations which can be quickly improved using generics.

Here’s a typical example where we have a set of interfaces for value objects inheriting a common base, and an implementation for each served up via a factory method.

Sample Java factoried pbject hierarchy

One simple factory approach is to have a method (or even a class and method pair) per interface:-

public class Factory1 {
       public static EmailMessage makeEmailMessage() {
              return new EmailMessageImpl();
       }

       public static TextMessage makeTextMessage() {
              return new TextMessageImpl();
       }
       …
}

This offers type-safety and no need to cast the results, but it generates factory bloat and additional code to maintain, since new factory methods have to be added when new types are introduced.

Another approach is to use a generic factory method returning the base interface type and creating the object based on a parameter:-

public class Factory2 {
       private static final Map<Class, Class> IMPLEMENTATIONS =
                     new HashMap<Class,Class>();

       public static Communication make(Class type)
                     throws InstantiationException, 
                            IllegalAccessException {
              return (Communication)
                     IMPLEMENTATIONS.get(type).newInstance();
       }
}

This keeps the factory bloat down but requires casting whenever the factory method is called:-

EmailMessage emailMessage = (EmailMessage)make(EmailMessage.class);

Adding generics

If you’ve seen this sort of code before you’ve probably also seen your code editor warning about Class being a raw-type. We can clean this up a little and make it more type-safe by parameterising our references to Class.

public class Factory3 {
       private static final Map<
              Class<? extends Communication>,
              Class<? extends CommunicationImpl>> 
           IMPLEMENTATIONS = new HashMap<
                   Class<? extends Communication>,
                   Class<? extends CommunicationImpl>>();

       public static Communication 
                     make(Class<? extends Communication> type)
                     throws InstantiationException, 
                                    IllegalAccessException {
              return IMPLEMENTATIONS.get(type).newInstance();
       }
}

By specifying what our classes extend using wildcards we’ve introduced some type-safety and also removed the need to cast the object returned by the make() method, though since we’re still returning the base type we still need to cast the manufactured object in order to use it.

To alleviate this, instead of using wildcards we can use type parameters which name a parameterised raw type so it can be referenced multiple times in the signature, in this case for both the parameter and the return type:-

public static <T extends Communication> T make(Class<T> type)
              throws InstantiationException, IllegalAccessException {
       return (T)IMPLEMENTATIONS.get(type).newInstance();
}

Here we’re defining a type T which extends Communication, specifying that our make() method returns an instance of that type and has a parameter which is a Class extending that type.

Which means that, since invocations of make() must now return an implementation of the supplied interface, we no longer need to cast our calls to it:-

EmailMessage emailMessage = make(EmailMessage.class);

Leave a Reply

Your email address will not be published. Required fields are marked *