Introduction
Design patterns are proven solutions to common software design problems. In this article, we’ll simplify the Factory Method pattern—explaining how it works, when to use it, and how to implement it with clear examples. The examples are inspired by Refactoring Guru and Dive Into Design Patterns (Alexander Shvets), distilled to their essence.
How Does the Factory Method Work?
The Factory Method is a creational design pattern that provides an interface for creating objects in a superclass while allowing subclasses to alter the type of objects created.
Example Scenario: A Restaurant Kitchen
Imagine a restaurant where:
- The
Kitchen
class defines how dishes are made. - Each dish (e.g.,
Pizza
,Burger
,Pasta
) follows a commonDish
interface with methods likeprepare()
. - Instead of hardcoding dish creation, the
Kitchen
delegates instantiation to subclasses (PizzaStation
,BurgerStation
).
This way, adding a new dish (e.g., Taco
) doesn’t require modifying the Kitchen
—just creating a new subclass.
When to Use the Factory Method?
Use this pattern when:
- You don’t know object types beforehand – The exact class of objects (e.g., dishes) isn’t known until runtime.
- You want extensibility – Users of your library/framework should be able to add new types (e.g., new dishes) without changing existing code.
-
You need object reuse – Avoid rebuilding objects by caching and reusing them (e.g., reusing a preconfigured
Pizza
object).
How to Implement the Factory Method
Step-by-Step (Restaurant Example)
-
Define a Common Interface
- All dishes implement a
Dish
interface with shared methods (e.g.,prepare()
,cook()
).
- All dishes implement a
-
Create the Base Factory (Kitchen)
- The
Kitchen
class declares an abstract (or default) factory method:
public abstract Dish createDish(String type);
- The
-
Implement Factory Subclasses
- Subclasses (
PizzaStation
,BurgerStation
) overridecreateDish()
to instantiate specific dishes:
public class PizzaStation extends Kitchen { @Override public Dish createDish() { return new Pizza(); // Pizza implements Dish } }
- Subclasses (
-
Optional: Use Parameters for Variants
- If a dish has variations (e.g.,
PastaType
), pass a parameter to the factory method instead of creating more subclasses:
public Dish createDish(String pastaType) { return new Pasta(pastaType); }
- If a dish has variations (e.g.,
-
Make the Factory Method Abstract (If Needed)
- If the base factory method has no default logic, declare it
abstract
to force subclasses to implement it.
- If the base factory method has no default logic, declare it
Why Avoid Hardcoding?
A bad alternative would be hardcoding every dish in the Kitchen
:
// ❌ Hardcoded (inflexible)
public class Kitchen {
public Pizza makePizza() { ... }
public Burger makeBurger() { ... }
// Requires changes for every new dish!
}
Problems:
- Violates the Open/Closed Principle (code isn’t open for extension).
- Becomes bloated as new dishes are added.