#8 Factory Method

Factory Method post banner

The Factory Method is a creational design pattern that defines an interface for creating an object, but lets subclasses decide which specific class to instantiate. This pattern allows delegating the responsibility of object creation, promoting decoupling and extensibility.

Scenario

In many software applications, we need to create objects that share a common interface but differ in behavior or configuration. When the client code handles object creation directly, it becomes tightly coupled to concrete classes. This makes the system harder to maintain and evolve over time.

Consider an application designed to control industrial ovens at a company specializing in metal heat treatment. Initially, the app supports only one oven model with a serial connection. As new models are introduced, first with TCP/IP communication, then with OPC UA, the code complexity quickly grows. The client code, responsible for setting the oven temperature, must now include logic for instantiating, configuring, and handling various types of connections. This leads to code duplication and makes the software maintenance and the addition of new ovens harder. We need a solution that centralizes object creation and decouples the client from the specifics of each connection type. This problem can be effectively addressed using the Factory Method pattern: it allows us to abstract object creation behind a common interface, placing the logic in a dedicated hierarchy. As a result, we can support new oven types without modifying the client code.

In the next section, we’ll dive deeper into how the pattern works and how it helps us write flexible, scalable, and more testable code.

Solution

The Factory Method addresses the issues above by introducing a base class that defines an abstract factory method. Subclasses implement this method to return specific concrete instances.

The client code (that is, the code calling the factory method) works with the base type and doesn’t know which specific class it’s using. This makes the application more flexible, easier to extend, and simpler to test.

Structure

The diagram below illustrates the classes and interfaces involved in the Factory Method pattern.

Factory Method generic class uml diagram
Class diagram of the Factory Method structure

The key components are:

  • Product: An interface (or abstract class) that defines the type returned by the factory method. It could also be a base class, the core concepts of the pattern remain the same.
  • ConcreteProduct 1, 2, etc.: Concrete implementations of the Product interface.
  • Creator: Defines (i.e., contains) the abstract factory method CreateProduct(), which returns an object of type Product.
  • ConcreteCreator 1, 2, etc.: Subclasses that override the factory method to return a specific ConcreteProduct.

Pros and cons

Pros

The Factory Method design pattern offers several advantages:

  • Decoupling from concrete classes: The client works with a common interface and doesn’t depend on concrete implementations, making the code more modular and adaptable.
  • Open-Closed Principle compliance: New product types can be added without changing existing code, in line with the Open-Closed Principle.
  • Separation of concerns: The client focuses on using the product, not on how it’s created.

Cons

  • Increased complexity: Introducing interfaces and subclasses may be overkill in very simple cases.
  • Code fragmentation: Object creation logic is spread across multiple classes.

When to use the Factory Method

The Factory Method pattern is particularly useful in the following scenarios:

  • When the exact type of object to create isn’t known in advance: If the object’s implementation depends on conditions that change at runtime, the Factory Method allows subclasses to decide which object to instantiate.
  • When a class wants to delegate the creation process to its subclasses: This is common in extensible systems where subclasses are expected to define the types they produce.
  • When object creation is complex or subject to change: For example, if the object depends on configuration files, environment settings, or runtime context (such as protocol type), the Factory Method provides a clean abstraction.
  • When you want to decouple client code from knowledge of concrete classes: This improves extensibility and minimizes the impact of future changes.

When to avoid the Factory Method

In general, avoid using the Factory Method when:

  • There’s only one concrete product class and no plan to add more. In this case, a Factory Method adds unnecessary complexity. A direct instantiation may be simpler and clearer.
  • The creation process is trivial and doesn’t involve any special logic. If creating the object is a straightforward operation without configuration or dependencies, a factory adds little value.
  • Performance is critical and object creation is a bottleneck. While the Factory Method introduces only minimal indirection, this overhead might matter in ultra-low-latency applications. That said, this is usually a micro-optimization and rarely a concern in typical projects.

Real-world example

Let’s revisit the industrial oven example. The ovens share common behavior, but differ in communication protocol. Here’s how the Factory Method can help us manage this variation. The application must:

  • Support both legacy ovens (serial connection) and modern ones (TCP/IP). The core logic (open connection, set temperature, close connection) is the same, but the connection details differ.
  • Allow for adding new oven types without modifying the existing client code.

The diagram below shows how the Factory Method can be applied.

Class diagram of a real-world example using the Factory Method pattern

Here’s the breakdown:

  • IDeviceConnection is the interface returned by the factory method. It exposes methods like Open(), SetTemperature(), and Close(), independent of the actual connection type. In terms of the generic structure, this is the Product interface.
  • SerialConnection and TcpConnection are concrete classes implementing IDeviceConnection, each with its own logic. These correspond to the ConcreteProduct classes.
  • OvenController is an abstract class that defines the factory method CreateConnection() and the method SetOvenTemperature(), which encapsulates the connection logic. This is the Creator class.
  • LegacyOvenController and ModernOvenController override CreateConnection() to return a SerialConnection or a TcpConnection, respectively. These correspond to the ConcreteCreator classes.

The client code works only with objects of type OvenController. It doesn’t need to care which connection is used: the Factory Method, defined in the concrete creator, will return the correct instance. This makes the code more modular, open to extension, and easier to maintain.

This approach allows you to easily extend support to new connection types (for example, OPC-UA) simply by creating new concrete creators, without touching the controller logic or the client code.

LabVIEW

Let’s now look at the LabVIEW implementation of the example described above. You can find the full code here.

The image below shows the Factory Method implementation: on the left, LegacyOvenController, and on the right, ModernOvenController. In this example, each concrete creator simply returns the appropriate instance of IDeviceConnection: LegacyOvenController returns a SerialConnection object, while ModernOvenController returns a TcpConnection object.

In practice, the created object may require specific initialization parameters (such as the serial port or IP address). This logic should also be encapsulated within the Factory Method, to maintain abstraction and avoid exposing construction details to the client code.
This way, you avoid scattering responsibilities in the client code and you respect the Single Responsibility Principle (SRP): the creation logic remains in the right place, making the system more maintainable and less tightly coupled.

The next image shows the SetOvenTemperature method of the OvenController class. This is a very common approach: you invoke the Factory Method (CreateConnection), which returns a correct and already initialized instance of type IDeviceConnection. Once you have the connection, the method works exclusively with the interface, without worrying about implementation details.

SetOvenTemperature encapsulates a well-defined sequence of operations: opening the connection, sending the temperature change command, and closing the connection. This structure reflects the Template pattern (which we’ll explore in an upcoming post), often used in combination with the Factory Method. The Template defines the general algorithm flow while delegating the creation of specific objects (in this case, the connection) to the Factory Method. The result is well-organized, extensible code with clearly separated responsibilities.

LabVIEW Factory Method SetOvenTemperature

The final image shows the client code. Here, both LegacyOvenController and ModernOvenController are instantiated. Although they represent two different oven types, the client can interact with both in the same way by simply calling the SetOvenTemperature method. This demonstrates one of the main advantages of the Factory Method: the client code remains independent of the specific concrete classes used, promoting reusability, testability, and openness to extension.

LabVIEW Factory Method code

In the next section, we’ll look at the actual C# implementation.

C#

For completeness, let’s now look at the implementation of the same example in C#. The full code is available here.

Here is the definition of IDeviceConnection and its implementations.

public interface IDeviceConnection
{
void Open();
void Close();
void SetTemperature(double temperature);
}

public class SerialConnection : IDeviceConnection
{
public void Open() => Console.WriteLine("Serial connection opened");
public void Close() => Console.WriteLine("Serial connection closed");
public void SetTemperature(double temperature) =>
Console.WriteLine($"[Serial] Temperature set to {temperature} °C");
}

public class TCPConnection : IDeviceConnection
{
public void Open() => Console.WriteLine("TCP connection opened");
public void Close() => Console.WriteLine("TCP connection closed");
public void SetTemperature(double temperature) =>
Console.WriteLine($"[TCP] Temperature set to {temperature} °C");
}

Below is the definition and implementation of the Factory Method:

// Abstract class that defines the Factory Method
public abstract class OvenController
{
public void SetOvenTemperature(double temperature)
{
var connection = CreateConnection();
connection.Open();
connection.SetTemperature(temperature);
connection.Close();
}

csharp
Copia
Modifica
    // This is the actual Factory Method definition
    protected abstract IDeviceConnection CreateConnection();
}

public class LegacyOvenController : OvenController
{
    // Factory method implementation
    protected override IDeviceConnection CreateConnection()
        => new SerialConnection();
}

public class ModernOvenController : OvenController
{
    // Factory method implementation
    protected override IDeviceConnection CreateConnection()
        => new TCPConnection();
}

Conclusions

In this post, we explored the Factory Method pattern, a creational design pattern with a particular focus on the principle of separating object creation from object usage. We saw how this approach encourages cleaner, more modular design aligned with the SOLID principles, especially the Single Responsibility Principle.

The Factory Method is often a first step in scenarios where flexibility in object creation becomes essential. In more complex systems, it can naturally evolve into patterns like Abstract Factory, useful when multiple families of objects must be instantiated.

It’s also worth noting that the Factory Method is often used together with the Template Method pattern, especially when a common structure of operations or algorithms is needed, while leaving specific steps, such as object creation, to subclasses.

The sample code discussed in this article is available at the following links: LabVIEW and C#.

In the next post, we’ll see how the Abstract Factory pattern builds upon these ideas, offering a higher level of abstraction for object creation.

When writing object-oriented code, understanding these patterns is not just an academic exercise. It often makes the difference between a project that evolves smoothly and one that becomes rigid at the first sign of change.

Leave a Comment

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

Scroll to Top