Design patterns Ep.2 - Creational - Abstract Factory
In the previous article we explored some details on the Simple Factory and Factory Method patterns. In today's article, we are going to examine another popular creational design pattern and in particular, the Abstract Factory pattern.
Problem
Let us understand the Abstract Factory Design Pattern with a simple example. We are going to develop an application for creating cross-platform UI elements without coupling the client code to concrete UI classes, while keeping all created elements consistent with a selected operating system.
Our requirement is, we will the take the current OS via an application setting or environment variable on Startup and try to construct various UI elements and render them to the screen. We will once again simulate the rendering of the UI elements, by simply writing some message in the console.
Let's imagine that our code consists of classes that represent:
We need to be able to create individual UI element objects, in a way that they match other objects of the same family. It would not be so convenient, to render in the GUI non-matching UI elements (e.g. one Windows button and then one Mac checkbox). Another thing we would like to have, is that we would not want to change existing client code, when adding new products or families of products to our program.
Solution
We can fulfill the above requirements by using the Abstract Factory Design Pattern, which is a creational design pattern that lets you produce families of related objects without specifying their concrete classes.
The first thing the Abstract Factory pattern suggests is to explicitly declare interfaces for each distinct product of the product family (e.g., Button, Checkbox). Then you can make all variants of products follow those interfaces. For example, all button variants can implement the Button interface. All checkbox variants can implement the Checkbox interface, etc.
The next move is to declare the Abstract Factory. The abstract factory is an interface with a list of creation methods for all products that are part of the product family (e.g. CreateButton, CreateCheckbox ). These methods must return abstract product types represented by the interfaces we extracted previously: Button, Checkbox etc.
Now, how about the product variants? For each variant of a product family, we create a separate factory class based on the AbstractFactory interface. A factory is a class that returns products of a particular kind. For example, the WindowsFactory can only create WindowsButton and WindowsCheckbox objects. On the other hand, the MacFactory can only create MacButton and MacCheckbox objects, and so on.
The client code has to work with both factories and products via their respective abstract interfaces. This lets you change the type of a factory that you pass to the client code, as well as the product variant that the client code receives, without breaking the actual client code.
Let's say, for example, that the client wants a factory to produce a button object. The client doesn’t have to be aware of the factory’s class, nor does it matter what kind of button it will get in return (whether it is a Windows style button or a Mac style button). Either way, the client must treat all button the same way, using the abstract Button interface. The only thing the client knows about the button is that it implements the Render method in some way. Also, whichever variant of the button is returned by the factory, we are sure that it will always match the type of the checkbox that the same factory object will produce. In other words, if the factory produces windows style button objects, then it will also be responsible to produce windows style checkbox objects as well.
Let's examine some details of the pattern and see some code in C#.
Structure
Participants
Recommended by LinkedIn
Although concrete factories instantiate concrete products, the return types of their creation methods must be the corresponding abstract products. This way the client code that uses a factory doesn’t get coupled to the specific variant of the product it gets from a factory.
Let's put all of these together and declare a main method, which will initialize our Application class, that will play the role of our Client code. The application picks the factory type depending on the current configuration or environment settings and creates it at runtime (usually at the initialization stage).
The output of the above code for the "Windows" variant would be:
On the other hand, if we changed our Main method's code and called the Initialize method, using the "Mac" variant the output would be:
Applicability
Use the Abstract Factory pattern when:
1) A system should be independent of how its products are created, composed and represented
2) A system should be configured with one of multiple families of products
3) A family of related product objects is designed to be used together, and you need to enforce this constraint
4) You want to provide a class library of products and you want to reveal just their interfaces, not their implementations
You can find the source code for the above example here.
That's all for today. Cheers!