#9 Abstract Factory Pattern – structure, benefits, and a real-world example

The Abstract Factory is a creational design pattern that allows the instantiation of a set of related or interdependent objects. This pattern abstracts the process of creating these families of objects, providing an interface to create interconnected objects without specifying their concrete classes. In practice, the Abstract Factory defines an interface for the creation of all objects within a certain family, ensuring that the produced objects are compatible and consistent with each other.
To better illustrate the function of the Abstract Factory, one can think of car manufacturing, where an abstract factory defines the creation of related parts like the engine, bodywork, and interior, ensuring compatibility between the specific components of a given model.

Scenario

In software design and development, we often encounter situations where we need to manage sets of objects that are also closely related or dependent on each other. Relying on the client code to directly create these groups of objects can lead to high coupling issues, making the system rigid and difficult to adapt to new requirements.

Consider the same example seen in the post Factory Method Pattern: an application for controlling industrial ovens in a company specialized in metal heat treatments, designed to remotely manage ovens that use different communication protocols. These ovens come from different manufacturers and models. Some use a serial connection for communication, others use network protocols. Some communicate with simple commands, while others require more structured messages. To communicate with them, the application needs to know how to connect, how to write the correct commands, and how to interpret the responses received from the ovens themselves.

For each type of oven, a small group of objects is therefore needed to work together: one that manages the communication, one that prepares the commands, and one that interprets the responses. The problem arises when these objects must be created in the code: if the application has to decide which type of object to use every time, the result is a complicated system that is difficult to modify or extend.

To resolve this complexity, the Abstract Factory comes into play: instead of creating these objects directly, the application relies on an “abstract factory” that defines which elements are needed for each type of oven and provides them all together, already compatible with each other. In this way, the code remains simple and focused only on the process logic, without having to worry about the implementation details.

In the next chapter, we will look in detail at how this pattern works and how it helps keep the code flexible, scalable, and easier to test.

Solution

As anticipated above, the Abstract Factory can be used to manage situations where it is necessary to instantiate objects of different types that are consistent with each other. The Abstract Factory thus abstracts the creation of a family of dependent or coherent objects without specifying their concrete classes. The Abstract Factory defines an interface by declaring several abstract creation methods (the Factory Methods), each responsible for creating a different type of object within the family.

The client code only works with the common interfaces provided by the Abstract Factory and the base classes, without ever directly instantiating the implementations. In this way, the client remains independent of the concrete classes, the system becomes more flexible, and it is possible to introduce new families of objects simply by implementing a new concrete class derived from the abstract factory.

In practice, the client asks the Abstract Factory for a consistent set of objects, uses them through their interfaces, and does not have to worry about the creation details. This approach reduces coupling, improves maintainability, and paves the way for a more extensible and easier-to-test system.

Structure

The following diagram shows the classes and interfaces involved in the Abstract Factory.

Abstract Factory Structure – UML Class Diagram

The involved entities are:

  • The interfaces AbstractProductA and AbstractProductB. They define the types of objects returned by the Abstract Factory. AbstractProduct could also be classes. This changes nothing to the concepts and structure of the pattern.
  • ConcreteProductA1, A2, B1, B2, etc. These are concrete classes that implement the Product interfaces.
  • AbstractFactory. It defines (i.e., contains) the individual factory methods, one for each type of Product.
  • Concrete Factory 1, 2, etc. These are the classes that implement the methods of the AbstractFactory. Each ConcreteFactory corresponds to a specific family of products and creates only those related products.

Advantages

The benefits resulting from the use of the Abstract Factory design pattern are:

  • Independence from concrete classes: the client code interacts with a common interface, without knowing the concrete classes, making it more flexible and independent of concrete implementations.
  • Adherence to the Open-Closed Principle: It is possible to add new product types without modifying existing code.
  • Separation of responsibilities: The client code focuses on using the object, not on its creation.
  • Product consistency: Ensures that products created by the same concrete factory are compatible with each other. A concrete factory produces an entire family of related objects. In this way, the risk of mixing incompatible products is avoided.
  • Encouragement of interface usage: The Abstract Factory, by its nature, encourages the programmer to work with interfaces instead of concrete classes.
  • Runtime configuration change: the client can select which concrete factory to use (e.g., based on a configuration file or user choice), obtaining a consistent family of objects without changing code.
  • Scalability: when the number of product families grows, the Abstract Factory makes it easier to maintain and organize variants.
  • Reduction of creation code duplication: centralizes the instantiation logic in one place, avoiding the repetition of constructors or setup logic scattered in the client.

Disadvantages

  • Increased complexity: the introduction of interfaces and subclasses can be overkill for simple scenarios.
  • Fragmentation: the creation logic is scattered across different classes.
  • Difficulty adding new products to the family: if a new type of product needs to be introduced (beyond those anticipated by the factory interface), all concrete factories must be modified. This reduces flexibility in that regard.
  • Steeper learning curve: for teams unfamiliar with design patterns, the Abstract Factory can seem complicated and unintuitive.

When to use the Abstract Factory

The Abstract Factory is particularly useful in the following scenarios:

  • When you need to work with families of related objects that must be used together (e.g., buttons + windows, parser + commands + connection).
  • When you want to ensure consistency among the created objects, avoiding incompatible combinations.
  • When you want to decouple the client from the knowledge of concrete classes, working only with interfaces or abstract classes.
  • When you want to be able to easily change the entire family of products (e.g., switch from Serial to TCP implementation, or from light to dark theme) without modifying the client code.

When to avoid the Abstract Factory

In general, it’s advisable to avoid the Abstract Factory when:

  • The concrete product classes never change. If you only have one implementation for each interface and do not plan to add others, introducing an Abstract Factory only adds unnecessary complexity. In these cases, a direct instance of the concrete class is simpler and clearer.
  • The problem is simple and the pattern only adds verbosity. If there are no families of objects or risks of inconsistency, the Abstract Factory risks burdening the design without bringing real benefits.
  • There are stringent performance constraints. The indirection introduced by the pattern entails a minimal overhead, but in applications with very stringent latency requirements, even this difference can be significant. In most cases, however, it is a negligible micro-optimization.

Real-world Example

Let’s revisit the industrial oven scenario above: we have models with different communication protocols but similar behavior. Some send and receive simple messages, others require more structured messages. To communicate with them, the application needs to know how to connect, how to write the correct commands, and how to interpret the responses received from the ovens themselves.

Now let’s see how to apply the Abstract Factory to manage this variability in the code. Specifically, the application must:

  • Manage ovens from different manufacturers, using different connections: serial and TCP/IP.
  • Support messages with different structures: XML and plain text.
  • Implement the same common logic for all ovens: opening the connection, sending the command for temperature change, sending the command to start the oven, reading the current temperature, and closing the connection.
  • Allow the addition of new oven types without modifying existing client code, while promoting the reuse of already developed code.

In other words, each oven doesn’t need a single object, but a small group of objects that work together: one for the connection, one for command creation, and one for response interpretation.

These objects must be consistent with each other: a oven that communicates via serial connection and uses plain text messages cannot be managed with an XML parser, and vice versa.

This is where the Abstract Factory comes in: instead of leaving the client code responsible for deciding which concrete classes to use, we delegate the creation of this group of objects to a specialized factory. In this way, the client only works with common interfaces and remains completely independent of the implementation details.

Class diagram of a real-world example using the Abstract Factory

In detail, we have:

  • The IConnection interface, which represents the connection to the oven (serial, TCP, etc.). It exposes the methods Open(), Send(), Read(), and Close(), regardless of the type of connection used. Compared to the general structure of the Abstract Factory, IConnection corresponds to an AbstractProduct.
  • The ICommandBuilder interface, which handles the creation of commands to send to the oven (BuildSetTemperature(), BuildStartCycle(), etc.). This interface is also an AbstractProduct.
  • The IResponseParser interface, which interprets the responses received from the oven (ParseTemperature() and ParseStatus(), etc.). This also represents an AbstractProduct.
  • The IOvenFactory interface declares the methods CreateConnection(), CreateCommandBuilder, and CreateResponseParser. Compared to the general structure of the pattern, this corresponds to the AbstractFactory class.
  • The classes LegacyOvenFactory and ModernOvenFactory, which implement IOvenFactory by creating coherent families of objects. For example, LegacyOvenFactory instantiates a SerialConnection, a PlainTextCommandBuilder, and a PlainTextResponseParser. Compared to the general structure, these correspond to ConcreteFactory 1 and ConcreteFactory 2.
  • The classes SerialConnection and TcpConnection, which implement IConnection by providing the specific logic for opening, writing, and closing communication. These classes are the ConcreteProduct associated with IConnection.
  • The classes PlainTextCommandBuilder and XmlCommandBuilder, which implement ICommandBuilder and produce plain text and XML messages, respectively. They represent the ConcreteProduct associated with ICommandBuilder.
  • The classes PlainTextResponseParser and XmlResponseParser, which implement IResponseParser by interpreting textual and XML responses, respectively. They represent the ConcreteProduct associated with IResponseParser.

Thanks to this organization, the addition of a new oven type does not require changes to the main logic, but only the implementation of a new factory and its corresponding concrete products. In this way, the system is extensible, organized, easier to maintain, and also allows for better reuse of already developed code.

The complete implementation of the example is available here: LabVIEW and C#.

Conclusions

The Abstract Factory is a natural evolution compared to the Factory Method. While the latter helps us manage the creation of a single object, the Abstract Factory allows us to create entire families of objects that are coherent with each other.

In our industrial oven example, this approach ensures that the IConnection, the ICommandBuilder, and the IResponseParser are always compatible, avoiding incorrect combinations such as a serial connection with an XML parser.

Another important advantage is reuse: the same implementations of connection, builder, and parser can be recombined to support new oven models simply by adding a new concrete factory, without modifying the existing client code.
For example, imagine a manufacturer updates the firmware of their ovens, maintaining TCP/IP communication but replacing XML messages with JSON messages, which are lighter and more widespread. In this case, neither the client nor the connection part needs modification: we can continue to use the already developed TcpConnection class, only creating a new JsonCommandBuilder and a JsonResponseParser. The only addition required is a new concrete factory, tasked with combining these objects.
Thanks to this organization, the transition to a new message format becomes much less invasive and does not affect the rest of the system, which remains stable and reuses a large part of the code already written.

In summary, the Abstract Factory helps us keep the code more flexible, organized, and resistant to changes, making system extension and reuse of already developed code simpler.

For those who wish to delve deeper, the complete implementation is available here: LabVIEW and C#.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top