Factory Method & Abstract Factory: Scaling Creation

Move beyond the Practical Factory. Learn how to use inheritance and interfaces to delegate object creation, ensuring your system follows SRP and OCP at scale.

April 22, 20244 min read2 / 2

In our Practical Factory lesson, we learned how to centralize if-else logic into a single class. But as your system grows, even a single Factory class can become a bottleneck or a violation of principles.

How do we scale object creation when we have dozens of types and complex dependencies? Enter the Factory Method and Abstract Factory patterns.


1. The Factory Method Pattern: "I Know Myself Best"

In the Practical Factory, the factory class holds the "mapping" (e.g., if season == WINTER return CROW). But what if the Season class itself could tell us which bird to create?

The Problem with Centralized Mapping

If you have a BirdFactory that maps seasons to birds, you have a separate class that needs to be updated every time a new season is added. If you forget to update the factory, your system breaks.

The Solution

Instead of a separate class with if-else, we add a Factory Method directly into the parent class or interface.

abstract class Season { // This is the Factory Method public abstract createHappiestBird(): Bird; } class Summer extends Season { @Override public createHappiestBird(): Bird { return new Peacock(); } }

Why use this?

  1. Strict OCP: When you add a Monsoon season, you must implement createHappiestBird(). You don't need to touch any other class.
  2. No If-Else: The client just calls season.createHappiestBird(). Polymorphism handles the rest.

2. The Abstract Factory: Solving the SRP Violation

The Factory Method works great for one object. But what if a class needs to create multiple related objects?

Imagine a Database interface. It might need to create a Query, a Transaction, and a SecurityLayer. If you add createTransaction(), createQuery(), and createSecurity() methods to the Database interface, you are violating the Single Responsibility Principle (SRP). The database is now responsible for being a database AND being a factory for three different things.

The Solution: Extracting the Factory

We extract all those factory methods into a dedicated Abstract Factory interface.

interface Database { connect(): void; // Returns the specialized factory createFactory(): DatabaseFactory; } interface DatabaseFactory { createQuery(): Query; createTransaction(): Transaction; }

Now, a MySQLDatabase will return a MySQLDatabaseFactory, which knows how to create MySQLQuery and MySQLTransaction.


Real-World Example: Cross-Platform UI (Flutter/React Native)

Think about building an app for both Android and iOS.

  • On Android, a button should look like a material design button.
  • On iOS, it should look like a Cupertino button.

Instead of writing if (platform == ANDROID) everywhere, Flutter uses an Abstract Factory.

  1. UI Interface: Has a method getComponentFactory().
  2. ComponentFactory (Abstract Factory): Has methods like createButton(), createMenu(), and createCheckbox().
  3. AndroidComponentFactory: Returns AndroidButton, AndroidMenu.
  4. iOSComponentFactory: Returns iOSButton, iOSMenu.

The client code just says: ui.getComponentFactory().createButton().display(). It doesn't care which platform it's running on!


3. The "Factory Triad": Working Together

In a real-world system like a Database Driver, you often use all three patterns together. This is the Composite Journey of an Object:

  1. Practical Factory: You use a DatabasePracticalFactory.getDatabase(url) to get the correct database object (e.g., MySQLDatabase) based on a config string.
  2. Factory Method: The MySQLDatabase object has a factory method createFactory() that returns a specialized factory.
  3. Abstract Factory: The returned MySQLDatabaseFactory provides the actual MySQLQuery or MySQLTransaction objects you need.

The Client Traversal Path:

DatabaseDatabaseFactoryQuery

This multi-stage handoff ensures that your main application logic is completely decoupled from the database vendor. You can swap MySQL for PostgreSQL just by changing a single string in your config file, and the rest of the chain handles itself!


Summary: Choosing Your Factory

FeaturePractical FactoryFactory MethodAbstract Factory
PurposeSimple object creation based on input.Delegates creation to subclasses via inheritance.Creates families of related objects.
LocationSeparate class with static methods.Method within the class hierarchy.Separate interface for creational methods.
ScaleBest for small-scale projects.Best for deep inheritance trees.Best for complex, cross-platform libraries.

Key Takeaway

While the Practical Factory is what you will explicitly "design" 90% of the time, the Factory Method and Abstract Factory often emerge naturally as you refine your architecture to follow SRP and OCP. They are the tools you use when your "Simple Factory" starts becoming too complex.


Further Reading

  • Refactoring Guru: Abstract Factory
  • Case Study: Explore the JDBC (Java Database Connectivity) API to see these patterns in action across dozens of different database vendors.

With this, we conclude our deep dive into the Factory family. In our next module, we will explore the Prototype & Registry Pattern, where we learn how to create perfect copies of objects without knowing their specific types.

Practice what you just read.

Quiz: Factory Method & Abstract Factory
1 exercise