by Zoran Horvat
Oct 01, 2014
When talking about abstract factories, it all begins with this definition:
"Provide an interface for creating families of related or dependent objects without specifying their concrete classes." [Gama et al., Design Patterns: Elements of Reusable Object-Oriented Software]
In this pattern central object is the client. It is the client's needs that are targeted by the pattern implementation. Primary goal of the abstract factory is to cut the client loose from its concrete dependencies that cannot be instantiated and provided to the client when it is initialized. Client can instantiate a class without knowing which concrete constructor to use.
Each concrete implementation of the factory provides access to constructor of one concrete product class. That is how the client can access constructors of classes that are potentially not known at compile time.
This is the basic setup for the abstract factory pattern. But there is the downside. Instead of having just the client and one or two concrete dependencies, we have ended up with seven distinct classes. Added complexity is the greatest enemy of abstract factories. In many cases this doesn't sound like a fair trade to developers.
However, in this article, I plan to give you one slightly different reason to use abstract factories and to accept the added complexity that they incur. As the result, complexity will actually be moved out of the client and into a specialized class which is, by pure coincidence, going to be the concrete factory itself.
Take a look at the following class diagram.
Class A instantiates class B at some point during execution of one of its methods. Class B in turn instantiates class C, which further instantiates class D. This object graph is not complicated. It is only four objects deep and it's relatively easy to understand how it works.
But now the situation begins to complicate. Suppose that the inner-most class D has two dependencies:
If these two dependencies are mandatory, then we would usually inject them through class D’s constructor. But now class C is in troubles because it must also know about these two dependencies. Therefore, class C will also receive both dependencies through its constructor. So will the class B and, ultimately, class A. This is what we get in the end:
Net result is that these two dependencies have suddenly become mandatory dependencies all the classes in the graph. Dependencies of inner objects propagate upwards all the way to the application root.
Let's continue and see what happens if other classes have dependencies of their own:
Class C receives another pair of dependencies. Class B adds one dependency of its own, and so does the class A.
We have reached a forest of dependencies. Class A pulls six dependencies, class B five, class C four and only class D remains with two of its dependencies alone. This class diagram indicates that dependencies have flooded over into classes that own target objects. Not only that classes A, B and C are needlessly complicated, but they also possess quite a lot of knowledge about implementation of classes down the stream. If class D is modified, so that it doesn’t depend on one of its current dependencies, all other classes will have to be modified as well. That is one notoriously bad consequence.
So what can we do to make the design presented above better? One technique that I normally use is to avoid instantiating classes directly, but instead to delegate that operation to concrete factories. Here is the modified design:
Let's start with the class A. Instead of instantiating class B directly, it will call the factory method to create and return an instance. Right now, class A has two dependencies, just like I wanted it to have. The only change is that this time it references factory of B rather than the class B itself.
Immediate result of this change is that now the B factory carries leaked dependencies. Class A is not dependent on these five dependency classes any more. B factory lets class A instantiate class B as before. Only this time, dependencies of B will be supplied by the concrete factory, rather than the class A.
This is the fundamental shift in the design, and this is the mechanism how abstract factory helps us contain proliferation of the dependencies. Nothing stops us from applying this mechanism further down the stream. After C factory has been introduced, class B only depends on that factory and its own natural dependency. Same goes with class C after D factory has been introduced.
Look at the class diagram once again and see how neatly the dependencies are isolated now. Each of the classes only depends on its actual dependencies and on the factory which can be used to create the next instance in the object graph.
Can you believe that these classes are doing precisely the same thing as those before? Class dependencies are now clearly separated. Class which instantiates another class does not require dependencies of that other class. Dependencies are safely stored in concrete factories. Observe this detail. Concrete factory keeps the dependencies. Abstract factory has nothing to do with the dependencies.
Not let me reflect on what I said earlier. Dependencies are implementation leaks. They tell us how the class implements its responsibility. Now how does this leak fit into the concrete factory? Well, concrete factory goes hand by hand with its concrete product. Therefore, it is a kind of normal for concrete product implementation to leak into the concrete factory. It would be a bad idea if implementation leaked into the abstract factory, or into other classes. But this way, I believe that classes are much better organized and easier to implement. Change in implementation of one class only affects its concrete factory and nothing else. Of course, Inversion of Control (IoC) container may be affected. But I consider IoC container to be the infrastructure, not implementation of the application. So I don't really care if it will ever suffer.
I have to admit that I was cheating a little with previous diagrams. You have probably noticed that all class diagrams are using concrete factories. This made class diagrams significantly easier to draw. The real thing is when factories and their products become abstract. Let me show you that without cheating, this time on a smaller scale.
In this case, there are only two classes. Class A instantiates class B and class B has a concrete dependency. The problem with class A here is the same as before – it becomes dependent on its product’s dependency. The dependency relation becomes transitive, to name it in mathematical terms. Or, in less formal terms, dependency becomes contagious.
Class A implementation depends on class B implementation, which indicates that implementation has leaked out of the class B into its client.
As before, we can prove that with a simple mind experiment. What happens if class B changes its implementation, so that then it depends on a completely different class? To support that modification, class A would also have to replace its dependency with the new one. This proves that implementation decision has leaked out of the class B and into the class A.
To solve the problem, I want to make class B abstract. This doesn’t necessarily mean that class B really has to be declared as an abstract class. Interfaces are the same in this respect. Remember, interfaces in C# are the same as pure abstract classes, i.e. classes with all methods declared abstract. But, since abstract classes and interfaces cannot be instantiated, class A now needs a mediator. That is the abstract factory which produces abstract product B. Here is the new class diagram:
To make the picture complete, I had to provide a concrete factory. This factory class depends on its concrete product. But it also depends on its product's dependency. Without the dependency readily available, factory object wouldn't be able to instantiate the product.
From this class diagram, class A is now oblivious of the dependency class. The price that I paid was adding three more types to the system: there are abstract and concrete factory and the abstract product. Value added is that now class A does not have to know anything about the dependency class. Dependency remains with the class B alone. With this implementation, I am free to redesign class B. I can remove or replace its dependency and class A’s implementation would remain exactly the same.
This whole benefit might not be of great importance if I only had classes A and B in the system. But if the system started to grow, as on previous diagrams, if I had classes C, D and E, all chained together as one another’s dependencies, then the dependency graph would quickly start to grow beyond comprehension. That is the situation in which chain of dependencies can be broken by inserting an abstract factory and its abstract product in place of a concrete product.
In this article, I have demonstrated one alternative application of the Abstract Factory pattern. Remember, the original pattern suggests that abstract factory can be used to provide multiple implementations of an interface. Abstract factory decouples the client from concrete implementation of a class it requires. It lets us dynamically switch from instantiating one concrete class to instantiating another concrete class.
Programmers often forget this, so let me tell it. It is not necessary for the product to have more than one concrete implementation in the application. It is sufficient to supply fake product in automated tests and we already have two implementations. So, writing tests is sometimes a sufficient reason to employ Abstract Factory pattern. But in this article, I have used the abstract factory for one completely different purpose.
I have already emphasized that in the examples shown here there were no multiple implementations of concrete classes. I have strictly taken care to have only one implementation of each product class. Yet, I felt urge to decouple client classes from product classes. The trick was in the dependencies. Concrete product dependencies become dependencies of the client. Abstract factory hides away the fact that concrete product depends on other classes. Only concrete factory knows this detail, but once again the client knows nothing about the concrete factory.
And in that way, the circle is broken. Concrete product's dependencies cannot leak upstream into the client. Dependencies are filled in by the IoC container and the client is saved from having to deal with them. That is the interesting application of the Abstract Factory pattern that I wanted to discuss in this article.
If you wish to learn more, please watch my latest video courses
In this course, you will learn how design patterns can be applied to make code better: flexible, short, readable.
You will learn how to decide when and which pattern to apply by formally analyzing the need to flex around specific axis.
This course begins with examination of a realistic application, which is poorly factored and doesn't incorporate design patterns. It is nearly impossible to maintain and develop this application further, due to its poor structure and design.
As demonstration after demonstration will unfold, we will refactor this entire application, fitting many design patterns into place almost without effort. By the end of the course, you will know how code refactoring and design patterns can operate together, and help each other create great design.
In four and a half hours of this course, you will learn how to control design of classes, design of complex algorithms, and how to recognize and implement data structures.
After completing this course, you will know how to develop a large and complex domain model, which you will be able to maintain and extend further. And, not to forget, the model you develop in this way will be correct and free of bugs.
Zoran Horvat is the Principal Consultant at Coding Helmet, speaker and author of 100+ articles, and independent trainer on .NET technology stack. He can often be found speaking at conferences and user groups, promoting object-oriented and functional development style and clean coding practices and techniques that improve longevity of complex business applications.