Skip to main content

Factory Method Pattern

Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

Problem​

You need to create objects without specifying their exact classes, and you want subclasses to decide which objects to create.

Common Scenarios:

  • UI frameworks creating different platform-specific components
  • Document readers creating different document types
  • Game engines creating different character types
  • Plugin architectures loading different plugins

Design Principles Applied​

  1. Depend on abstractions, not concrete classes - Dependency Inversion Principle
  2. Program to an interface - Use product interface
  3. Open/Closed Principle - Add new products without modifying existing code

UML Diagram​

Implementation​

Example 1: Pizza Store (Head First Design Patterns)​

// Product interface
public abstract class Pizza {
protected String name;
protected String dough;
protected String sauce;
protected List<String> toppings = new ArrayList<>();

public void prepare() {
System.out.println("Preparing " + name);
System.out.println("Tossing dough: " + dough);
System.out.println("Adding sauce: " + sauce);
System.out.println("Adding toppings:");
toppings.forEach(topping -> System.out.println(" " + topping));
}

public void bake() {
System.out.println("Bake for 25 minutes at 350");
}

public void cut() {
System.out.println("Cutting the pizza into diagonal slices");
}

public void box() {
System.out.println("Place pizza in official PizzaStore box");
}

public String getName() {
return name;
}
}

// Concrete Products - NY Style
public class NYStyleCheesePizza extends Pizza {
public NYStyleCheesePizza() {
name = "NY Style Sauce and Cheese Pizza";
dough = "Thin Crust Dough";
sauce = "Marinara Sauce";
toppings.add("Grated Reggiano Cheese");
}
}

public class NYStylePepperoniPizza extends Pizza {
public NYStylePepperoniPizza() {
name = "NY Style Pepperoni Pizza";
dough = "Thin Crust Dough";
sauce = "Marinara Sauce";
toppings.add("Grated Reggiano Cheese");
toppings.add("Sliced Pepperoni");
toppings.add("Garlic");
toppings.add("Onion");
toppings.add("Mushrooms");
}
}

// Concrete Products - Chicago Style
public class ChicagoStyleCheesePizza extends Pizza {
public ChicagoStyleCheesePizza() {
name = "Chicago Style Deep Dish Cheese Pizza";
dough = "Extra Thick Crust Dough";
sauce = "Plum Tomato Sauce";
toppings.add("Shredded Mozzarella Cheese");
}

@Override
public void cut() {
System.out.println("Cutting the pizza into square slices");
}
}

public class ChicagoStylePepperoniPizza extends Pizza {
public ChicagoStylePepperoniPizza() {
name = "Chicago Style Pepperoni Pizza";
dough = "Extra Thick Crust Dough";
sauce = "Plum Tomato Sauce";
toppings.add("Shredded Mozzarella Cheese");
toppings.add("Black Olives");
toppings.add("Spinach");
toppings.add("Eggplant");
toppings.add("Sliced Pepperoni");
}

@Override
public void cut() {
System.out.println("Cutting the pizza into square slices");
}
}

// Creator (abstract)
public abstract class PizzaStore {
// Factory method
protected abstract Pizza createPizza(String type);

// Template method that uses factory method
public Pizza orderPizza(String type) {
Pizza pizza = createPizza(type); // Factory method call

pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();

return pizza;
}
}

// Concrete Creators
public class NYPizzaStore extends PizzaStore {
@Override
protected Pizza createPizza(String type) {
switch (type) {
case "cheese":
return new NYStyleCheesePizza();
case "pepperoni":
return new NYStylePepperoniPizza();
default:
return null;
}
}
}

public class ChicagoPizzaStore extends PizzaStore {
@Override
protected Pizza createPizza(String type) {
switch (type) {
case "cheese":
return new ChicagoStyleCheesePizza();
case "pepperoni":
return new ChicagoStylePepperoniPizza();
default:
return null;
}
}
}

// Test
public class PizzaTestDrive {
public static void main(String[] args) {
PizzaStore nyStore = new NYPizzaStore();
PizzaStore chicagoStore = new ChicagoPizzaStore();

Pizza pizza = nyStore.orderPizza("cheese");
System.out.println("Ethan ordered a " + pizza.getName() + "\n");

pizza = chicagoStore.orderPizza("cheese");
System.out.println("Joel ordered a " + pizza.getName() + "\n");
}
}

Example 2: Document Creator​

// Product interface
public interface Document {
void open();
void save();
void close();
}

// Concrete Products
public class PDFDocument implements Document {
@Override
public void open() {
System.out.println("Opening PDF document");
}

@Override
public void save() {
System.out.println("Saving PDF document");
}

@Override
public void close() {
System.out.println("Closing PDF document");
}
}

public class WordDocument implements Document {
@Override
public void open() {
System.out.println("Opening Word document");
}

@Override
public void save() {
System.out.println("Saving Word document");
}

@Override
public void close() {
System.out.println("Closing Word document");
}
}

public class ExcelDocument implements Document {
@Override
public void open() {
System.out.println("Opening Excel document");
}

@Override
public void save() {
System.out.println("Saving Excel document");
}

@Override
public void close() {
System.out.println("Closing Excel document");
}
}

// Creator
public abstract class Application {
// Factory method
public abstract Document createDocument();

public void newDocument() {
Document doc = createDocument();
doc.open();
// Do work with document
doc.save();
doc.close();
}
}

// Concrete Creators
public class PDFApplication extends Application {
@Override
public Document createDocument() {
return new PDFDocument();
}
}

public class WordApplication extends Application {
@Override
public Document createDocument() {
return new WordDocument();
}
}

public class ExcelApplication extends Application {
@Override
public Document createDocument() {
return new ExcelDocument();
}
}

Dependency Inversion Principle​

Bad Design (High-level depends on low-level):

public class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza;

// Depends on concrete classes!
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
}

return pizza;
}
}

Good Design (Both depend on abstraction):

// High-level module
public abstract class PizzaStore {
protected abstract Pizza createPizza(String type); // Abstraction
}

// Low-level modules
public class NYPizzaStore extends PizzaStore {
protected Pizza createPizza(String type) { // Implements abstraction
// Create concrete pizzas
}
}

Benefits​

✅ Loose coupling

  • Creator and products are decoupled
  • Easy to add new products

✅ Single Responsibility

  • Product creation logic in one place
  • Creator doesn't know concrete product classes

✅ Open/Closed Principle

  • Add new products without modifying existing code

✅ Dependency Inversion

  • Depend on abstractions, not concretions

Drawbacks​

❌ Code complexity

  • Need to create subclass for each product type
  • More classes to maintain

❌ Parallel hierarchies

  • Product hierarchy and Creator hierarchy must be kept in sync

When to Use​

✅ Use Factory Method When:

  • Class can't anticipate type of objects to create
  • Class wants subclasses to specify objects created
  • Parent class chooses creation of objects to subclasses

❌ Don't Use When:

  • Simple object creation suffices
  • No variation in product creation
  • Abstract Factory: Creates families of related objects
  • Template Method: Often uses Factory Method
  • Prototype: Alternative to Factory Method for object creation

Best Practices​

  1. Use enums for type-safe product selection
  2. Consider Simple Factory for simpler cases
  3. Combine with Dependency Injection for better testability
  4. Document creator responsibilities clearly

Summary​

Factory Method delegates object creation to subclasses, promoting:

  • Loose coupling between creator and products
  • Adherence to Dependency Inversion Principle
  • Easy extension with new product types

Key Takeaway: Let subclasses decide which concrete class to instantiate, keeping the creator independent of specific product implementations.