Design patterns Ep.2 - Creational - Abstract Factory

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:

  • A family of related products (UI element), for example: Button, Checkbox
  • Several variants of the family, for example Button and Checkbox classes come in the following variants (operating systems in our example): Windows, Mac

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

No alt text provided for this image

Participants

  • Abstract Products (IButton, ICheckbox): Declare interfaces for a set of distinct but closely related products, that make up a product family

No alt text provided for this image
No alt text provided for this image

  • Concrete Products (WindowsButton, WindowsCheckbox, MacButton, MacCheckbox): Various implementations of abstract products, grouped by variants. They define product objects to be created by the corresponding concrete factory. Each concrete product must implement its corresponding AbstractProduct interface and each abstract product (IButton / ICheckbox) must be implemented in all given variants (Windows / Mac).

No alt text provided for this image
No alt text provided for this image
No alt text provided for this image
No alt text provided for this image

  • The Abstract Factory interface (IGUIFactory): Declares a set of methods for creating each of the abstract products

No alt text provided for this image

  • Concrete Factories (WindowsFactory, MacFactory): Implement the operations to create concrete product objects. Each concrete factory corresponds to a specific variant of products and creates only those product variants (e.g. Windows, Mac etc.)

No alt text provided for this image
No alt text provided for this image

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.

  • Client: Uses only interfaces declared by AbstractFactory and AbstractProduct classes. It can work with any concrete factory / product variant, as long as it communicates with their objects via abstract interfaces.

No alt text provided for this image

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).

No alt text provided for this image

The output of the above code for the "Windows" variant would be:

No alt text provided for this image

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:

No alt text provided for this image

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!

To view or add a comment, sign in

More articles by Orestis Meikopoulos

Insights from the community

Others also viewed

Explore topics