Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 50

Mastering Factory Patterns for Decoupled Object Creation

Recommended resources

Hello! Welcome to your next lesson.

In the last module, we focused on the principles of good design (SOLID), spotting bad design (code smells), and fixing it (refactoring). That was about improving existing code. Now, we shift our focus to creating well-designed code from the start. We're entering the world of Design Patterns: reusable, proven solutions to commonly occurring problems within a given context in software design.

This lesson kicks off our study of Creational Patterns, which are all about how objects are created. Your goal is to implement the Factory Method and Abstract Factory creational patterns to decouple object creation. We will explore how these patterns help you write flexible, scalable code that adheres to the SOLID principles we've discussed, particularly the Open/Closed Principle.

1. The Core Problem: The Tight Coupling of new

In much of our code, we create objects directly: myObject = new MyObject(). While simple, this approach tightly couples the client code (the code that uses the object) to the concrete class (MyObject).

Imagine you're building an application that processes different types of documents: PDF, DOCX, TXT. Your code might look like this:

// A simplified example
if (fileType.equals("pdf")) {
    document = new PDFDocument();
} else if (fileType.equals("docx")) {
    document = new DOCXDocument();
} else {
    document = new TXTDocument();
}
document.process();

What happens when you need to add a CSVDocument? You have to find and modify this if/else block. If this logic is scattered in multiple places, the task becomes risky and tedious. This design violates the Open/Closed Principle: it's not open for extension without modification.

This is the exact problem factory patterns are designed to solve.


2. A First Step: The Simple Factory Idiom

A common first step to clean this up is to encapsulate the creation logic into a single, dedicated class. This is often called a "Simple Factory." While not an official design pattern, it's a crucial stepping stone.

The Factory Method Pattern Explained and Implemented in Java | Creational Design Patterns | Geekific

Let's start with a practical example that illustrates this core problem. This video from Geekific explains why directly instantiating objects can become a maintenance nightmare and introduces the 'Simple Factory' as a first step to fix it.

Watch from the beginning to 03:43. Pay attention to how the initial code for creating burgers violates the Open/Closed Principle, and how encapsulating this logic in a separate 'factory' class improves the design, even if it's not a complete solution yet.

The Simple Factory centralizes creation logic, which is an improvement (it follows the Single Responsibility Principle). However, the factory class itself must still be modified whenever a new product type is added. We can do better.


3. The Factory Method Pattern

The Factory Method pattern provides a complete solution. It defines an interface for creating an object but lets subclasses decide which class to instantiate.

How It Works

The key idea is to move from a single factory class to an abstract Creator class that declares a factoryMethod(). Concrete subclasses then override this method to produce specific products. This relies on inheritance.

Let's see how this is applied to the burger example.

The Factory Method Pattern Explained and Implemented in Java | Creational Design Patterns | Geekific

The Simple Factory is a good start, but it isn't fully 'closed' for modification. Now, let's see how the official Factory Method pattern solves this by using inheritance to delegate object creation to subclasses.

Continue watching from 03:43 to 07:34. This section defines the Factory Method, refactors the burger example to use it, and explains the pattern's structure with a UML diagram. Note how the Restaurant becomes an abstract creator and specific restaurants (BeefBurgerRestaurant) become concrete creators.

Structure and Vocabulary

To formalize what you just saw, let's look at the standard terminology and structure of the pattern.

Factory Method

The Refactoring.guru website is an excellent resource for clear, structured breakdowns of design patterns. Let's use it to learn the formal vocabulary.

Read the sections 'Solution' and 'Structure'. This will give you the formal definitions for Product, Concrete Product, Creator, and Concrete Creator. Compare these terms to the elements in the video's burger example (e.g., Burger is the Product, BeefBurger is a Concrete Product, Restaurant is the Creator).

The structure can be visualized with a standard UML diagram.

Factory Method – Class diagram
A UML diagram for the Factory Method pattern. It shows the key components: an abstract `Creator` with a `factoryMethod()`, `ConcreteCreator`s that implement this method, a `Product` interface, and `ConcreteProduct`s that implement the interface. The client code interacts with the `Creator`'s interface, decoupling it from the specific product classes.

With this pattern, adding a new ChickenBurger doesn't require modifying any existing code. You simply create a new ChickenBurger class (a ConcreteProduct) and a new ChickenBurgerRestaurant class (a ConcreteCreator). The system is now open for extension but closed for modification.

Test your understanding!

You are designing a logging system. You want to be able to log messages to different destinations, such as a file, a database, or a remote web service. The core application logic shouldn't care where the messages are being logged, only that it has a Logger object with a log(message) method.

How would you apply the Factory Method pattern here? Identify the classes that would correspond to:

  1. Product
  2. Concrete Products
  3. Creator
  4. Concrete Creators
Show answer
  1. Product: An interface or abstract class named Logger with a log(message) method.
  2. Concrete Products: FileLogger, DatabaseLogger, and WebServiceLogger. Each would implement the Logger interface.
  3. Creator: An abstract class named LoggerFactory with an abstract method createLogger().
  4. Concrete Creators: FileLoggerFactory, DatabaseLoggerFactory, and WebServiceLoggerFactory. Each would implement createLogger() to return an instance of its corresponding logger (e.g., FileLoggerFactory returns a new FileLogger()).

4. The Abstract Factory Pattern

The Factory Method is perfect for creating a single type of object. But what if your system needs to create families of related objects that are designed to work together?

For instance, imagine a UI toolkit that supports multiple operating systems (Windows, macOS). A theme for Windows should have a WindowsButton and a WindowsCheckbox. A theme for macOS should have a macOSButton and a macOSCheckbox. You must never mix them; a WindowsButton with a macOSCheckbox would break consistency.

This is where the Abstract Factory pattern shines. It provides an interface for creating families of related or dependent objects without specifying their concrete classes. It's like a "factory of factories."

How It Works

The Abstract Factory pattern introduces an AbstractFactory interface that declares a set of factory methods, one for each product in the family (e.g., createButton(), createCheckbox()). Concrete factories then implement this interface to produce a specific family of objects that are guaranteed to be compatible.

The Abstract Factory Pattern Explained and Implemented | Creational Design Patterns | Geekific

Let's see how this works in practice. This next video from Geekific addresses the problem of creating related product families using a great example of computer components from different manufacturers.

Watch from 00:21 to 05:56. The video uses an example of GPUs and Monitors. Notice how trying to use a single factory for multiple product types breaks the Open-Closed principle. Pay close attention to how the solution involves: An abstract factory interface (Company) with multiple factory methods (createGpu, createMonitor). Concrete factories (MsiManufacturer, AsusManufacturer) that produce a specific family of products.

Use Cases and Structure

The Abstract Factory pattern is powerful for enforcing consistency across a set of objects. This is fundamental to building scalable, maintainable architectures.

Explained Design Patterns: Factory Method and Abstract...

To broaden your understanding, let's explore more use cases. This article provides excellent real-world examples where Abstract Factory is a great fit.

Read the sections 'Abstract Factory Pattern' and its 'When to Use' subsection. Focus on the core idea of ensuring consistency among a family of objects (e.g., a Windows button with a Windows checkbox). The 'Real-World Use Cases' section is particularly valuable for developing your architectural mindset.


5. Factory Method vs. Abstract Factory

It's easy to confuse these two patterns, but their intent is different. Here's a summary to help you distinguish them:

Aspect Factory Method Abstract Factory
Intent Create a single object. Create a family of related objects.
Mechanism Uses inheritance. Subclasses override a creation method. Uses composition. The client gets a factory object and uses it.
Interface A single create() method. An interface with multiple createProductX(), createProductY() methods.
Analogy A pizza restaurant. You decide if you want a NYStylePizzaStore or ChicagoStylePizzaStore. A car factory. A FordFactory produces a FordEngine and FordTires. A ToyotaFactory produces a ToyotaEngine and ToyotaTires.

Often, the methods within an Abstract Factory are implemented as Factory Methods. The two patterns can work together.

Test your understanding!

You are designing a theming engine for a web application. A theme consists of a Button, a TextBox, and a Panel, which must all have a consistent look and feel (e.g., "Dark Theme" components vs. "Light Theme" components).

Which pattern, Factory Method or Abstract Factory, is more appropriate for creating the UI components for a selected theme? Justify your choice.

Show answer

Abstract Factory is the correct choice.

Justification: The problem requires creating a family of related objects (Button, TextBox, Panel) that must be consistent with each other.

  • An AbstractUIFactory interface would define methods like createButton(), createTextBox(), and createPanel().
  • Concrete factories like DarkThemeFactory and LightThemeFactory would implement this interface.
  • The DarkThemeFactory would create DarkButton, DarkTextBox, and DarkPanel objects, ensuring the entire UI is visually consistent.

Using Factory Method here would be difficult, as you'd need separate factories for each component, and there would be no guarantee that the client would use a DarkButton with a DarkTextBox. Abstract Factory solves this by bundling the creation of the entire theme's components into one object.


Conclusion

You've now learned about two foundational creational patterns that are essential for building flexible and maintainable systems. By moving object creation out of your client code and into dedicated factories, you decouple your components and embrace the Open/Closed Principle—a cornerstone of good software architecture.

Key Takeaways:

  • Directly calling new creates tight coupling and makes code hard to extend.
  • Factory Method uses inheritance to let subclasses decide which single type of object to create, decoupling the client from the concrete product.
  • Abstract Factory uses composition to provide an interface for creating families of related objects, ensuring they are compatible with each other.
  • Choosing the right pattern depends on whether you are creating a single object or a consistent set of objects.

Preview of the Next Lesson:
We will continue our exploration of creational patterns by examining the Singleton pattern. It has a very different goal: ensuring that a class has only one instance and providing a global point of access to it. We will implement it and, just as importantly, evaluate its use cases and significant drawbacks.