Is Dependency Injection Replacing the Factory Patterns?
Jakob Jenkov |
Dependency Injection (DI) has been a hot topic since at least 2003 and onwards. Lots of dependency injection frameworks have appeared (including my own Butterfly DI Container), and lots of forums and blogs has discussed the technique. Yet I still experience big enterprise software development departments, who have not yet embraced the idea. Instead I see them using the Factory design patterns extensively in situations when dependency injection would have been a far more flexible solution to the same problem.
Of course this is sometimes the case when old code is being maintained, which was written, or at least started, before dependency injection became mainstream. The factory pattern is then adhere to for consistency with the old code. But sometimes I also see the factory pattern used in completely new code.
This text will show how dependency injection is the next evolutionary step after the Factory design patterns, in the persuit of loosely coupled components.
The Purpose of the Factory Design Patterns
The purpose of the factory patterns is to separate the use of a certain component, from the choice of implementation + instance management of that component. These three separate concerns are repeated here:
- Component Use
- Component Implementation Choice
- Component Instance management
In the following sections I will show how the factory patterns achieve the separation of these concerns.
Static Factory
The static factory hides the choice of implementation and instance management of a certain component, from the objects using this component.
Here is first an example of how the code would look without a static factory:
public class MyClass { IMyComponent component = new MyComponent(); public void myMethod(){ component.doSomething(); } }
The IMyComponent
is an interface which is implemented by the MyComponent
class. In this example the MyClass
depends on the concrete implementation of
IMyComponent
- the MyComponent
class. It also has hardcoded, that
a new instance of MyComponent
is created every time a MyClass
instance is created.
To decouple MyClass
class from MyComponent
you can separate them
by a static factory. Here is an example of a factory called MyComponentFactory:
public class MyComponentFactory { public static IMyComponent instance(){ return new MyComponent(); } }
And here is the MyClass
using the factory instead of instantiating MyComponent
directly:
public class MyClass { IMyComponent component = MyComponentFactory.instance(); public void myMethod(){ component.doSomething(); } }
Now MyClass
doesn't know what implementation of IMyComponent
it gets,
nor if it's a new or cached instance.
Abstract Factory
The static factory design still has a few drawbacks though. What if you want to be able to switch the implementation at runtime? In other words, you want to be able to switch the factory at runtime. In addition you want to be able to use different factories in action from within different components. This design doesn't allow that.
To solve this problem you can use the abstract factory pattern instead of the static factory pattern.
Rather than calling a static instance()
method you first obtain a factory of a certain kind,
and then ask that factory for instances. Here is how the MyClass
class could look after
that little change:
public class MyClass { IMyComponent component = null; public MyClass() { IMyComponentFactory factory = MyComponentFactoryManager.getFactory("A"); component = factory.instance(); } public void myMethod(){ component.doSomething(); } }
And here is how the corresponding MyComponentFactoryManager
and IMyComponentFactory
interface would look:
public interface IMyComponentFactory { IMyComponent instance(); }
public class MyComponentFactoryManager { private static Map<String, IMyComponentFactory> factories = new HashMap<String, IMyComponentFactory>(); public static setFactory( String factoryId, IMyComponentFactory factory){ factories.put(factoryId, factory); } public static IMyComponentFactory getFactory(String factoryId){ return factories.get(factoryId); } }
As you can see, it is now possible to add, replace and remove factories at runtime. Smart, eh?
But even the abstract factory pattern has a drawback: The factory id is
hard coded inside the MyClass
! If both a MyClassA
and MyClassB
was referring to factory "A", then replacing
factory "A" in the MyComponentFactoryManager
class would
affect both MyClassA
and MyClassB
. Unless, of
course, you execute the factory replacement just before activating
either MyClassA
or MyClassB
, and set the factory
back to what it was, just prior to the factory replacement. But that kind
of stuff gets clumsy.
In addition, MyClass
still depends on the MyComponentFactoryManager
class to obtain it's instance, AND the IMyComponentFactory
interface to obtain
a IMyComponent
instance. Actually, the IMyComponent
instance
was all the MyClass
class was ever interested in, but now it is forced to know
and extra class (MyComponentFactoryManager
) and an extra interface
(IMyComponentFactory
) to get to the IMyComponent
instance. Clumsy, clumsy, clumsy!
The ServiceLocator Pattern
If you are familiar with the ServiceLocator design pattern you will notice that this pattern is very similar to the Factory pattern, and thus has the same problems.
Describing the ServiceLocator pattern is outside the scope of this text, since I'll describe what I believe to be a better solution anyways: Dependency Injection. To get more information about the ServiceLocator pattern, do a google search on it.
How Dependency Injection Solves the Problem
When using dependency injection the responsibility of obtaining needed instances is reversed. A class no longer obtains these instances itself. Instead, a dependency injection container creates the needed instances, and "injects" them into the object.
Here is how MyClass
would look when prepared for dependency injection (in its constructor):
public class MyClass { IMyComponent component = null; public MyClass(IMyComponent componentImpl) { component = componentImpl; } public void myMethod(){ component.doSomething(); } }
Now MyClass
doesn't know anything about the factory classes anymore. It only knows about
the IMyComponent
interface. It doesn't either know whether the injected IMyComponent
instance is a new instance or a cached instance. You can also easily switch the implementation injected
into e.g. class MyClassA
and MyClassB
independently from each other.
Additionally, as a side effect MyClass
has been easier to unit test because you can now
inject a mock (fake) IMyComponent
instance into it, during unit testing. The mock object
can then record what methods the MyClass
calls on it, and your unit test can then
check if the methods were called as expected.
Of course the drawback of this approach is that you now need to configure the dependency injection container
to inject the IMyComponent
instances into the MyClass
instances. Here is
a configuration example using Butterfly DI Container's
configuration script language, Butterfly Container Script:
myComponent = * com.myapp.MyComponent(); myClass = * com.myapp.MyClass(myComponent);
First an "object factory" called "myComponent
" is defined. It returns a new instance
of com.myapp.MyComponent()
every time that factory is called. The * means "new instance".
Second an object factory called "myClass
" is defined. This is also defined as a "new instance"
per factory call. Into the MyClass
constructor is injected an instance of whatever the
"myComponent
" factory returns when calling it (which is a new instance of MyComponent
).
So, when you ask the container for a "myClass
" instance the container first creates a
"myComponent
" instance, then injects it into the constructor of a MyClass
instance, as configured in the "myClass
" factory. Smart, isn't it?
Finally I'll just show how you obtain a "myClass
" instance once the container is configured:
MyClass myClassInstance = (MyClass) container.instance("myClass");
Pretty simple.
Summary
As you can see, a class prepared for dependency injection is much cleaner, and easier to test, than a class using any of the factory patterns. It is also easier to switch what is injected into different classes which use the same interfaces.
The drawback of dependency injection is that you need a dependency injection container, and you need to configure it. However, you can have so many benefits from a dependency injection container in addition to just cleaner code, that I really think it is worth it!
Tweet | |
Jakob Jenkov |