Factory Design Pattern
Define an interface for creating objects while allowing subclasses to decide the instantiation
The Factory Method Pattern: Stop Creating Objects Like a Caveman
You know what’s tedious? Scattering new keywords all over your codebase like confetti at a wedding. You know what’s worse? Trying to change which objects get created later and having to hunt down every single one of those new calls.
Enter the Factory Method pattern, here to save you from yourself.
What Problem Are We Even Solving?
Let’s say you’re building an app that creates different types of burgers. The naive approach looks like this:
if (type.equals("AMERICAN")) {
burger = new AmericanBurger();
} else if (type.equals("ORIENTAL")) {
burger = new OrientalBurger();
}
This seems fine until you realize:
- This logic is probably duplicated in multiple places
- Adding a new burger type means changing code everywhere
- Your client code is tightly coupled to concrete burger classes
- Testing becomes a nightmare
The Factory Method pattern says: “Let’s move all that creation logic to one place and make it someone else’s problem.”
The Restaurant Analogy That Actually Works
Think of a restaurant:
- Customer (Client): Just wants food, doesn’t care how it’s made
- Kitchen (Factory): Knows how to coordinate food creation
- Chef (Concrete Factory): Actually makes the specific dish
- Dish (Product): The thing you actually want to eat
The customer doesn’t jump behind the counter and start cooking. They place an order. The kitchen figures out which chef should make it. The dish appears. Magic.
Building Our Burger Factory, Step by Step
Step 1: Define What All Burgers Have in Common
public interface Burger {
void prepare();
}
This is our contract. Every burger, no matter what type, must be able to be prepared. Simple.
Step 2: Create the Actual Burgers
public class AmericanBurger implements Burger {
@Override
public void prepare() {
System.out.println("Preparing American Burger");
}
}
public class OrientalBurger implements Burger {
@Override
public void prepare() {
System.out.println("Preparing Oriental Burger");
}
}
Each burger knows how to prepare itself. This is the Single Responsibility Principle in action: the burger class is responsible for burger behavior, not for knowing when it should be created.
Step 3: The Abstract Factory (The Important Part)
public abstract class BurgerStore {
protected abstract Burger createBurger();
public void orderBurger() {
Burger burger = createBurger();
burger.prepare();
}
}
This is where things get interesting. The BurgerStore class defines the overall workflow (ordering a burger), but it doesn’t know which specific burger it’s creating. That’s delegated to subclasses.
Notice the abstract keyword. This class is saying: “I know the general process, but some details need to be filled in by someone else.”
Step 4: Concrete Factories (Where the Magic Happens)
public class AmericanBurgerStore extends BurgerStore {
@Override
protected Burger createBurger() {
return new AmericanBurger();
}
}
public class OrientalBurgerStore extends BurgerStore {
@Override
protected Burger createBurger() {
return new OrientalBurger();
}
}
Each store type knows which burger it makes. The American store makes American burgers. The Oriental store makes Oriental burgers. Shocking, I know.
Step 5: Using It All
public class Main {
public static void main(String[] args) {
BurgerStore store = new AmericanBurgerStore();
store.orderBurger();
store = new OrientalBurgerStore();
store.orderBurger();
}
}
Look at that client code. Clean. Simple. The client has no idea which concrete burger class is being used. It just knows it’s getting a burger.
What’s Actually Happening Behind the Scenes
Let’s trace through one order:
- Client picks a store type (AmericanBurgerStore)
- Client calls
orderBurger() - The parent class (BurgerStore) orchestrates the process
- When it needs a burger, it calls
createBurger() - The call goes to the subclass (AmericanBurgerStore)
- Subclass creates an AmericanBurger
- Parent class calls
prepare()on it - Burger is prepared and returned
The beauty here is separation of concerns. The parent class knows the process. The child class knows the specifics.
The Keywords That Confuse Everyone
Why abstract?
When you mark a class as abstract, you’re telling Java: “This class is incomplete on purpose. Don’t let anyone create an instance of it directly.”
new BurgerStore(); // Compiler error - can't do this
Why? Because BurgerStore has an abstract method (createBurger()) that doesn’t have an implementation. It wouldn’t know what to do if you called it.
An abstract class exists to be extended, not instantiated.
implements vs extends: The Eternal Confusion
These two keywords trip up beginners constantly. Here’s the deal:
implements is for interfaces:
public class AmericanBurger implements Burger {
// Must implement prepare() method
}
This means: “I promise to follow the contract defined in the Burger interface.”
extends is for classes:
public class AmericanBurgerStore extends BurgerStore {
// Inherits orderBurger() and must implement createBurger()
}
This means: “I’m building upon an existing class, reusing its behavior and filling in the gaps.”
Quick reference:
| Keyword | Used With | Meaning |
|---|---|---|
extends | class → class | ”I’m inheriting behavior” |
implements | class → interface | ”I’m promising to follow rules” |
When Should You Use Factory Method?
Use it when:
- Object creation is complex or might change
- You want to decouple client code from concrete classes
- You have a family of related products
- You know you’ll need to add new types later
Real-world examples:
- UI frameworks creating different button types
- Database connection managers
- Document parsers (PDF, Word, etc.)
- Payment processors (credit card, PayPal, crypto)
The Downsides Nobody Talks About
Let’s be honest: Factory Method adds complexity. You’re creating more classes and more abstractions. For simple cases, it’s overkill.
If you only have one type of burger and you’ll never add another, just use new AmericanBurger() and call it a day. Don’t over-engineer.
The pattern makes sense when:
- You have multiple product types
- Those types might change or expand
- You want flexibility in your codebase
How This Relates to SOLID Principles
Single Responsibility Principle: The factory worries about creation, products worry about behavior. Each class has one job.
Open/Closed Principle: Want to add a new burger type? Create a new burger class and a new store class. You don’t modify existing, tested code. You extend the system.
This is the real power of the pattern. Your codebase becomes open for extension but closed for modification.
Common Mistakes
Mistake 1: Creating the factory but still using new in the client code. If you’re going to use the pattern, commit to it.
Mistake 2: Making the factory do too much. The factory creates objects, it doesn’t manage their entire lifecycle or contain business logic.
Mistake 3: Using this pattern when a simple constructor would do. Not everything needs a factory.
The Mental Model That Helps
Think of the factory pattern as a vending machine. You select what you want (press a button), the machine handles the details of getting it to you, and you receive your product. You don’t care which specific motor mechanism grabbed your snack or how the internal conveyor belt works. You just know you’ll get what you asked for.
Final Thoughts
The Factory Method pattern is about delegation and abstraction. You’re moving object creation away from the client and into specialized classes that know how to do it right.
Is it necessary for every project? No. Is it useful when your object creation logic gets complex or varies? Absolutely.
Start simple. If you find yourself copying object creation code all over the place, or if adding new types requires changing code in ten different files, that’s your sign to consider a factory.
And remember: the goal of design patterns isn’t to make your code look fancy. It’s to make it maintainable, testable, and easy to change. Use the pattern when it solves a real problem, not just because it exists.