Skip to main content

🏭 Abstract Factory

You ever showered, and were lost in thought about how to create families of related objects? Fear not, Abstract Factories are here to help.

Thats what a abstract factory does: Creating abstract variants (wedding) of objects (Cake) within a family (pastries).

Thats what a abstract factory does: Creating abstract variants (wedding) of objects (Cake) within a family (pastries). (Image by author, illustrations by Takashi Mifune under free use)

🌍 Real-World Analogy​

Abstract Factory can be compared to a pastry shop that produces different types of products. Just like how a pastry shop has standardized processes for making their products, an Abstract Factory has a standardized process for creating objects.

The pastry shop has different products, but all of them are within the same family. Products can be categorized into certain abstract objects (e.g., Cake) and concrete objects (e.g., Wedding Cake)

The journey from Family to Concrete Objects β€” visualized.

The journey from Family to Concrete Objects β€” visualized. (Image by author, illustrations by Takashi Mifune under free use)

Other examples would be creating different types of buttons, or different types of vehicles.

For each concrete object, there is a certain β€œcookbook recipe” for creating them. In a pastry shop context, it would be making batters, forming cakes, baking and decorating.

Similarly, an Abstract Factory has different factories for creating families of related objects. Just like how a pastry shop can produce different types of pastries and variants with the same standardized process, an Abstract Factory can produce different types of objects.

πŸ€” Problem​

Imagine working on a Christmas gift for your kids: A game where they run a pastry shop. Your code will consist of classes that represent:

  1. A family of related objects, say: Cake + Pie + Tart. (Yes, these are completely different things)
  2. Variants of objects of said family, say: Cake + Pie + Tart may be available in multiple variants: Wedding, Birthday, Graduation.

Product families and their variants.

Product families and their variants. (Image by author, illustrations by Takashi Mifune under free use)

You need to create individual products that match others within the same family. Imagine: Your customers might be mad, if they receive products that do not match the occasion or that cannot be eaten at all.

Pie isn’t good for a birthday β€” but the customer is always right. Right?

Pie isn’t good for a birthday β€” but the customer is always right. Right? (Image by author, illustrations by Takashi Mifune under free use)

In addition, new products or variants might be added frequently. You don’t want to touch the existing code all over again.

πŸ’‘ Solution​

First, the Abstract Factory pattern is about explicitly declaring interfaces for distinct products within the family (e.g. Cake, Pie and Tart). Then, you add variants of products that follow that interface. For example, you can add variants for Wedding, Birthday and Regular cakes, and so on.

Example with inheritance

Example with inheritance (Image by author, illustrations by Takashi Mifune under free use)

Next, you need to create the Abstract Factory β€”an interface with creation methods for all products within the product family (e.g. createCake(), createPie() and createTart()). The return value must be abstract product types represented by the previously declared interfaces: Cake, Pie and Tart.

Each Factory stands for a specific variant of product within the family.

Each Factory stands for a specific variant of product within the family. (Image by author, illustrations by Takashi Mifune under free use)

Now, we have the individual products within the family. But how about the specific product variants? For each of the variants, we create a separate Factory class based on the Abstract Factory interface. It’s going to be a factory class that returns products of a specific kind. For example, the BirthdayFactory can only create products related to birthdays.

The client now needs to work with both factories and the products via their abstract interfaces. Like that, you can change the type of factory during runtime, by passing it as an argument. That way, you can select the product variant that the business logic receives without breaking it.

If the business logic wants a factory to produce a Cake, it does not need to be aware of the Factory class, nor does the kind of Cake matter. Whether it’s a Birthday Cake or a Wedding Cake, the business logic treats all cakes the same way, using their abstract ICake interface. Like that, the business logic only knows about the Cake, that it implements getEaten() in some way.

Instanciating the correct Factory within the BusinessLogic.

Instanciating the correct Factory within the BusinessLogic.

Here’s still one more thing to explain: The Factories need to be created in some way. Usually, the business logic is creating these Factories upon initiation. But the business logic still needs to figure out which Factory is the right one, depending on a manual configuration or environment settings.

πŸ—οΈ Structure​

Structure overview

Structure overview

  1. Abstract Products Declares a commonly shared interface for a set of similar but slightly different products β€” these make up the family.
  2. Concrete Products The concrete implementations. Each product within the family must have a concrete implementation in all variations.
  3. Abstract Factory Declares commonly shared interfaces for the different types of Factories.
  4. Concrete Factories Implements methods for creating each of the abstract products.
  5. Business Logic Used for instantiating, referencing and selecting the concrete Factories.
public abstract class ProductA
{
public abstract void Operation();
}

public class ConcreteProductA1 : ProductA
{
public override void Operation()
{
Console.WriteLine("Concrete product A1 implemented");
}
}

public class ConcreteProductA2 : ProductA
{
public override void Operation()
{
Console.WriteLine("Concrete product A2 implemented");
}
}

public interface IAbstractFactory
{
public abstract ProductA CreateProductA();
}

public class FactoryA1 : IAbstractFactory
{
public override ProductA CreateProductA()
{
return new ConcreteProductA1();
}
}

public class FactoryA2 : IAbstractFactory
{
public override ProductA CreateProductA()
{
return new ConcreteProductA2();
}
}

How to implement​

  1. Make a list of all types of products and their variations.
  2. Use abstract product interfaces to define the making of each product. Implement specific product classes implementing these interfaces.
  3. Create an abstract factory and its methods for all abstract products.
  4. Make concrete Factory classes for each product variant.
  5. Define the factory initialization and selection process. Instantiate each concrete factory class once. Pass it to all places in your business logic where products are being constructed.
  6. Replace direct product constructor calls with the appropriate factory creation methods.

Pros & Cons​

βœ… Pros​

βœ… Compatibility. Objects from the factory will be compatible with each other.

βœ… Avoiding tight coupling. between concrete products and the client logic.

βœ… Single Responsibility Principle. The product creation code can be extracted into one place, making the code easier to maintain.

βœ… Open/Closed Principle. New product variants can be introduced without breaking the existing business logic.

❌ Cons​

❌ Might overcomplicate things. The code might become more complicated than it could be. A lot of additional interfaces and classes are produced for this pattern β€” which might not be needed. Factory Method might be a more lightweight choice to start out.

❌ Inflexibility. The abstract factory pattern can be less flexible than other creational patterns since changing the product families or adding new products might require modifying the abstract factory interface and all its implementations.

❌ Harder to understand. The abstract factory pattern can be more difficult to understand and implement than other creational patterns, especially for developers who are new to design patterns. But, don’t worry β€” that’s why we’re here.

πŸ”— Relation with other patterns​

  • Factory Method is how designs usually start β€” over time they might evolve to Abstract Factory, Prototype or Builder.
  • Abstract Factory, Builders and Prototypes can be implemented as Singleton.
  • Abstract Factory classes are frequently based on Factory Method themselves, but it is also possible to use Prototype for composition of the class methods.
  • Abstract Factory can be an alternative to Facade if your intention is to hide how specialized objects are initialized.
  • Builder constructs very complex objects step by step. On the other side, Abstract Factory is used for families of related objects.
  • Abstract Factory can be paired with Bridge when some structures can only work with specific implementations. In that case, Factory Method is used to hide complexity.