ntroduction
This article discusses the strength and weakness of applying Dependency Injection (DI) with a variant of Abstract Factory design pattern. This approach is especially suitable for such scenarios as creating local stateful objects with dynamic parameters, handling checked exceptions thrown during object creation, and dynamically wiring up objects. IoC frameworks, such as Spring IoC container, PicoContainer and Guice, do not offer good solutions for these issues or simply unable to address them.
Abstract Factory Design Pattern for Dependency Injection
The classical GoF Abstract Factory design pattern is modified inthe following two ways:
- A factory interface replaces the abstract factory class (Optional),
- Every factory method is responsible for creating an object and injecting its dependencies.
Consider a simple example below:
Basically ComponentA depends on ComponentB. To enable unit-testing class ComponentAImpl, the implementation of interface ComponentB must be injected into ComponentAImpl. The Java code below shows the details of how to use a variant of Abstract Factory design pattern to do the dependency injection.
//Abstract Factory for Dependency Injection
//Factory interface
public interface Module1ServiceFactory {
ComponentA getComponentA();
ComponentB getComponentB();
}
//Concrete factory
public class Module1ServiceFactoryImpl implements Module1ServiceFactory {
private ComponentA componentA;
private ComponentB componentB;
private Module1Servicefactory instance;
private Module1ServicefactoryImpl() {}
public static synchronized Module1ServiceFactory getInstance() {
if (null == instance) {
instance = new Module1ServicefactoryImpl();
componentA = new ComponentAImpl();
componentB = new ComponentBImpl();
componentA.setComponentB(componentB);
}
return instance;
}
public ComponentA getComponentA() {
return componentA;
}
public ComponentB getComponentB() {
return componentB;
}
}
//Client
public class Client {
public static void main(String[] args) {
Module1ServiceFactory m1sf =
Module1ServicefactoryImpl.getInstance();
ComponentA componentA = m1sf.getComponentA();
componentA.operationA1();
}
}
Lazy-instantiation
Lazy-instantiation can be achieved by changing the method implementation such that the objects are created and wired up only when they needed. For instance, getComponentA() can be modified to:
re>public synchronized ComponentA getComponentA() { if (null == componentA) { componentA = new ComponentAImpl(); } return componentA; }
Of course Module1ServiceFactoryImpl.getInstance() should be modified not to create objects.
We can add a parameter for Module1ServiceFactoryImpl .getInstance() to decide whether it should be responsible for creating objects or not.
Non-Singleton Scope
The above code will create singleton objects. If a new object is needed for each invocation of getComponentA() and getComponentB(), then the factory implementation has to be modified:
//Concrete factory
public class Module1ServiceFactoryImpl {
private Module1ServiceFactory instance;
private Module1ServiceFactoryImpl() {}
public static synchronized Module1ServiceFactory getInstance() {
if (null == instance) {
instance = new Module1ServiceFactoryImpl();
}
return instance;
}
public ComponentA getComponentA() {
ComponentA componentA = new ComponentAImpl();
ComponentB componentB = getComponentB();
componentA.setComponentB(componentB);
return componentA;
}
public ComponentB getComponentB() {
return new ComponentBImpl();
}
}
We can also inject a singleton object into a non-singleton object. For example, if ComponentB is a singleton, and ComponentA is a non-singleton, then we do the following:
//Concrete factory
public class Module1ServiceFactoryImpl {
private Module1ServiceFactory instance;
private ComponentB componentB;
private Module1ServicefactoryImpl() {}
public static synchronized Module1ServiceFactory getInstance() {
if (null == instance) {
instance = new Module1ServiceFactoryImpl();
componentB = new ComponentBImpl();
}
return instance;
}
public ComponentA getComponentA() {
ComponentA componentA = new ComponentAImpl();
componentA.setComponentB(componentB);
return componentA;
}
public ComponentB getComponentB() {
return componentB;
}
}
Though it is possible to inject a non-singleton object into a singleton, it is rare that such a scenario will ever occur to a real world application. However, it is very common that a non-singleton object need to be created using dynamic parameters and assigned to a local variable. We discuss this scenario next.
Creation of Local Stateful Objects with Dynamic Parameters for Singletons
This problem poses challenge to all IoC frameworks. It is illustrated in the following method for ComponentA singleton:
public void operationA2() {
String s = aPrivateMethod();
int i = anotherMethod();
ComponentC componentC = new ComponentCImpl(s, i);
//do something else.
}
Here ComponentAImpl uses ComponentC. Obviously we need to inject the implementation of ComponentC, that is, ComponentCImpl, in order to unit-test ComponentAImpl.operationA2(). However, we cannot make componentC an instance variable since it is stateful object with client-specific state and should not be shared by other clients via threads. Thus it cannot be injected using normal setter or constructor injection.
There are two approaches to resolve this problem using Abstract Factory pattern. In both schemes, we need to modify Module1ServiceFactory and add a parametered factory method:
ComponentC getComponentC(String s, int i);
And the implementation of this method is added to Module1ServiceFactoryImpl:
public ComponentC getComponentC(String s, int i) {
return new ComponentCImpl(s, i);
}
The first approach is to inject the factory object into the class where the local stateful objects are needed:
private Module1ServiceFactory factory;
public void setModule1ServiceFactory(Module1ServiceFactory factory) {
this.factory = factory;
}
ComponentAImpl.operationA2() becomes:
public void operationA2() {
String s = aPrivateMethod();
int i = anotherMethod();
ComponentC componentC = factory.getComponentC(s, i);
//do something else.
}
The disadvantage of this approach is that ComponentAImpl now depends on Module1ServiceFactory and we need to provide a mock Module1ServiceFactory implementation in order to unit-test ComponentAImpl. Even with this shortcoming, injecting factory object into a class for creating local stateful objects is the simplest approach. The similar technique is widely used in J2EE technology. For instance, in JPA (Java Persistence API), an entity manager factory may be injected into the application code, and an application-managed entity manager is then created from the entity manager factory.
The second way to create the local stateful objects is to move most functionality into an abstract class, which is to be unit-tested, and have the dependent objects instantiated in the subclass, which is not meant to be unit-tested.
public abstract class AbstractComponentA implements ComponentA {
public void operationA2() {
String s = aPrivateMethod();
int i = anotherMethod();
ComponentC componentC = getComponentC(s, i);
//do something else.
}
public abstract ComponentC getComponentC(String s, int i) ;
}
public class ComponentAImpl extends AbstractComponentA {
public ComponentC getComponentC(String s, int i) {
return new ComponentCimpl(s, i);
}
}
This approach is similar to Springframework’s Method Injection except that the latter only supports the case where no dynamic parameter is needed for creating the stateful objects. The unit-testing code does not need to implement any mock factory. However, this is an awkward solution. Suppose we have ten such local stateful objects in class, then we need ten abstract methods for the sole purpose of enabling unit-testing. It indeed makes unit-testing possible and simple – at the expense of cluttered application code.
Springframework also offers method replacement which can also solve this problem using Java reflection. But it is an even more complicated approach and is not suitable for regular application code.
Checked Exceptions Thrown during Object Creation
This is another scenario where an IoC container is not a good fit. If checked exceptions are thrown during object creation process, the application may wish to catch and recover from them. Consider a web service client class. If the web service is not available when the client tries to create the web service stub, the client can catch the exception that the stub throws, display proper messages to users and ask them to try later on. It is difficult for IoC containers to throw any application-specific checked exceptions. Manually-coded factories can easily handle this situation – we can simply declare what checked exceptions are to be thrown in the throws clause of factory methods and leave the application code to handle and recover from them.
Wiring up Objects Dynamically
There are scenarios where different implementations of the same interface need to be injected to an object dynamically. A good example is Strategy design pattern. A parameter may be used to decide which concrete class to be injected. It is difficult to implement this type of dependency injection in XML-based IoC containers, which basically provide static wiring up of objects. The programmatic IoC containers may be able to resolve dependency dynamically, however, manual dependency injection with Abstract Factory provides the simplest and straightforward solution in this scenario, as shown in the example below:
//Concrete factory
public class Module1ServiceFactoryImpl {
...
public ComponentA getComponentA(int strategy) {
ComponentA componentA = new ComponentAImpl();
ComponentB componentB = getComponentB(strategy);
componentA.setComponentB(componentB);
return componentA;
}
public ComponentB getComponentB(int strategy) {
switch(strategy) {
case STRATEGYA:
return new StrategyA();
case STRATEGYB:
return new StrategyB();
default:
return null;
}
}
}
In this example, both StrategyA and StrategyB implement the interface ComponentB.
Concluding Thoughts
The suitable cases for applying dependency injection with Abstract Factory design pattern include creating local stateful objects from dynamic parameters, handling checked exceptions thrown during object creation, and wiring up objects dynamically. Besides, this approach has better performance compared with IoC containers since it uses straightforward Java code and hardwiring. The major disadvantage is that developers have to write more code to get started. Another drawback is that factory implementation code changes significantly if we change between lazy-initialization and eager-initialization or from singletons to non-singletons. However, it can be argued that it is rare that we need to make these kinds of changes.
The key for enabling unit-testing is programming to interfaces so classes do not rely on other concrete classes in the application. The mock objects can then be injected into the class under test and fulfill all dependency.
Dependency has to be resolved, one way or the other, somewhere in our applications. A common idea behind all IoC containers and manual DI solutions is that the dependency is resolved in a very few well-known and dedicated places – XML configuration files (Spring IoC), special Java classes (Google Guice), or concrete factory classes. In this way, we avoid spreading dependency all over application code and any concrete class can be easily swapped. Mock objects can then be applied to enable unit-testing.
All IoC containers – declarative or programmatic, can be view as general-purpose factory where objects are created and wired up, and the hand-coded Abstract Factory is always, of course, a customized DI solution.
And finally, with the IoC containers, we eliminate the dependency on concrete classes and make unit-testing possible, at the price of creating dependency on thirty-party APIs and/or XML configuration files. Since there is no standard for IoC containers and each framework provides different features and functionality around dependency injection, it is very difficult to replace one IoC framework with another.