This first part of this article is entirely based on a YouTube video by Christopher Okhravi. The second part is based on the book Head First Design Patterns.


Christopher Okhravi: Factory Method Pattern

The Factory Method Pattern is an important idea in how we design software, especially when we talk about how to create new objects. In this explanation, we will look closely at the Factory Method, see how it’s different from a Simple Factory, and get ready to understand the Abstract Factory later.

The book “Head First Design Patterns” says there are three kinds of factory patterns: Simple Factory, Factory Method, and Abstract Factory. The book also says that the Simple Factory isn’t really a design pattern. I agree with this, so we won’t talk about the Simple Factory as its own pattern. But, some of the ideas in the Simple Factory will help us understand the Factory Method, because the Factory Method is like a better, more flexible version. Our main focus here is the Factory Method, and we’ll talk about the Abstract Factory next time.

Why We Need Factories

In software that uses objects, we create many different objects from different plans (which we call classes). Then, we use these objects to do different things in our program. If you remember the Strategy pattern, we talked about “wishing” we already had an object that could do something. We would just assume it was there, often by having it given to us (this is called dependency injection). This way, we don’t have to worry about making the object exactly where we use it.

But, the important thing is that at some point, every object we use has to be made. We have to use the word new (or something similar) to actually create the object from its class. If we are using objects in our program (and not just using simple tools called static methods, which is usually not a good idea), then we have to create objects somewhere. So, the question is, where should we do this?

The Factory Pattern, in general, tries to solve this by saying we should put the job of creating objects in one place. This makes sure that we create objects in a consistent way everywhere in our program. By using a special thing called a factory, the factory is in charge of making the right kind of object.

At first, it might seem silly to just put a wrapper around the word new. If the only difference is saying new Animal() or calling AnimalFactory.createAnimal(), you might wonder what the point is. But, there are two important reasons why this is useful:

  1. Making Objects Can Be Complicated: Sometimes, to create an object, we need to do more than just say new. We might need to do some calculations or follow some business rules to decide what kind of object to make or what information to give it when we make it. For example, to make an Animal, we might need to decide if it should be a Dog, a Cat, or something else, based on some rules.
  2. Flexibility in How We Make Objects: If the factory itself is an object, we can change it while our program is running. This means we can change the way we make objects by using a different factory. This is a powerful idea called polymorphism.

A Simple Example: Making Animals

Imagine we have different kinds of animals in our program, like Dog, Cat, and Duck. All of these are types of Animal. In some parts of our program, we might need to create an Animal without knowing exactly if it should be a Dog, a Cat, or a Duck at that moment. If we knew exactly what kind of animal we needed, we could just have it given to us. But when we don’t know, a factory can help.

Also, the decision of which animal to create might depend on certain rules. For example, if we are building a program that pretends to be a park, we might want to create different animals based on chance or to make sure we have a good mix of animals.

This logic, the rules for creating objects, can be put into a factory. So, a factory is responsible for a specific way of creating objects of a certain type. In our animal example, we could have a RandomAnimalFactory that creates animals randomly, and a BalancedAnimalFactory that tries to create a balanced number of each type of animal over time. Both factories would create Animal objects, but they would use different ways to decide which specific kind of animal to make.

UML Diagram

The standard way to draw the Factory Method pattern uses a few main parts:

  • Product: This is like a general plan or an agreement for the objects we want to create (for example, Animal).
  • ConcreteProduct: These are the actual objects that follow the Product plan (for example, Dog, Cat, Duck).
  • Creator: This is a general plan or an agreement for the factories that will create the Product objects (for example, AnimalFactory). It might also have some basic ways of creating objects that the specific factories can change if they need to.
  • ConcreteCreator: These are the specific factories that follow the Creator plan and know how to create specific ConcreteProduct objects (for example, RandomAnimalFactory knows how to create Dog, Cat, or Duck randomly; BalancedAnimalFactory knows how to create them in a balanced way).

Here’s the UML Diagram:

The important part here is the factoryMethod(). This is a special instruction in the Creator that says how to create a Product object. Each ConcreteCreator will follow this instruction but will create its own specific kind of Product.

The different parts have different jobs: the Creator and the ConcreteCreator are in charge of how to make objects of a certain type. The Product and the ConcreteProduct are the types of objects that get made.

The Simple Factory is a simpler idea that doesn’t have the Creator part. It’s just one factory that knows how to make different kinds of products. While it’s easier to understand at first, it’s not as flexible as the Factory Method.

An Example in Making Games

Imagine we are making a game where we have asteroids. We need to create new asteroids while the game is running. A factory could be in charge of making these asteroids. At first, a simple AsteroidFactory might just make asteroids with different sizes and starting positions randomly. But, as the game goes on, different levels might need asteroids to act differently, like being bigger or faster.

Instead of making a new factory for each level (like LevelOneAsteroidFactory, LevelTwoAsteroidFactory), the Factory Method pattern lets us do it in a better way. We can have a general plan for making asteroid factories (AsteroidFactory), and then we can have specific factories that know how to make asteroids with certain qualities.

We can even give parameters to the factory to change how it makes asteroids. For example, we could tell the factory how likely it is to make a big asteroid or how fast the asteroid should move. This way, we can use the same factory to create different kinds of asteroids for different levels, just by giving it different instructions. For easy levels, we give it instructions to make small, slow asteroids. For hard levels, we tell it to make big, fast ones. This approach moves the creation logic out of those particular levels.

This shows how the Factory Method pattern helps us build things by putting smaller parts together (composition) instead of just building on top of each other (inheritance). Instead of having many different factory classes for different situations, we can have a few key factory ideas that we can combine and give parameters to, to create many different behaviors. This makes our code easier to manage and change.

Christopher Okhravi: Abstract Factory Pattern

Let’s talk about the Abstract Factory Pattern. If you already know about the Factory Method Pattern, understanding this will be very easy. The Abstract Factory Pattern is quite similar to the Factory Method Pattern. In fact, an Abstract Factory can be thought of as a group of Factory Methods. This means an Abstract Factory uses multiple Factory Methods.

Here’s the definition of the Abstract Factory Pattern from the book:

“The Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.”

The main difference between the Abstract Factory Pattern and the Factory Method Pattern is simple:

  • The Factory Method Pattern creates just one object.
  • The Abstract Factory Pattern creates multiple objects.

This might seem surprisingly simple for a pattern with such a cool name! But like “dependency injection,” while the technical action is simple, the power comes from where and how you use it. If you truly understand and apply it strategically, your system can become much more flexible.

From Factory Method to Abstract Factory: The Evolution

Let’s first remember the Factory Method Pattern’s setup:

  • You have a Factory Interface (or abstract class) which has different Concrete Factories that implement it. This factory has a Factory Method (let’s call it getProduct) that returns some kind of Product.
  • The Product itself is an interface or an abstract class, with many different Concrete Products that implement it.
  • The idea is that a general factory creates products. Depending on which specific Concrete Factory you choose, it will create different Concrete Products, perhaps with different logic or settings.

Now, how do we turn this into an Abstract Factory Pattern? The simple change is that a factory doesn’t just have onegetProduct method. It has multiple getProduct methods. For example, it might have getProductA and getProductB.

This means any Concrete Factory (the specific implementation of the factory) must now implement both getProductAand getProductB. So, one concrete factory can produce two or more products.

Here’s the UML diagram:

The definition says the Abstract Factory creates “families of related or dependent objects.” What does this mean?

Imagine you have many different kinds of products (like Product X, Y, Z, M, N, etc.). In your application, maybe only certain pairs or groups of these products make sense together. For example, maybe Product X and Product Y always go together, but Product X and Product N should never be used together.

A common example where the Abstract Factory Pattern is very useful is when building User Interfaces (UIs) that need to work on different operating systems (platforms), like macOS, Windows, and Linux. Let’s say our “products” are UI controls, like a window, a button, or a label:

  • Maybe Product X is a dialogue box for macOS, and Product Y is the “OK” button for macOS.
  • Then Product M could be a dialogue box for Windows, and Product N is the “OK” button for Windows.

The key point is that you generally wouldn’t mix a macOS alert window with a Windows-style button. They just don’t look right or work correctly together. If you only used the Factory Method Pattern to create buttons and alert boxes separately, you wouldn’t have control over accidentally mixing a macOS alert with a Windows button. But with the Abstract Factory Pattern, you gain that control.

How Abstract Factory Controls “Families”

The Abstract Factory Pattern gives you control because a single factory creates multiple related products. In our UI example, one factory would create both the alert box and the button:

  • Concrete Factory could be specifically a “macOS Factory.” When you ask this macOS Factory for an alert and a button, you are guaranteed to get a macOS-style alert and a macOS-style button.
  • Another Concrete Factory could be a “Windows Factory,” ensuring you get Windows-style UI controls.

This means when you call getProductA and getProductB from the same factory instance, you know that Product Aand Product B will be compatible and belong to the same “family” (e.g., both macOS, both Windows). Here’s the update UML diagram:

A Deeper Look at the Structure

Most examples of the Abstract Factory Pattern show that the factory’s methods return different types of products. For example, getProductA returns an object of ProductA, and getProductB returns an object of ProductB.

So, you’d have:

  • Abstract Factory: An interface or abstract class with methods like getProductA() (returns ProductA) and getProductB() (returns ProductB).
  • ProductA: An abstract class or interface for the first kind of product.
  • ProductB: An abstract class or interface for the second kind of product.
  • Concrete ProductA1, A2, A3…: Specific versions of ProductA.
  • Concrete ProductB1, B2, B3…: Specific versions of ProductB.
  • ConcreteFactory1, 2, 3…: Each of these factories implements the Abstract Factory interface. ConcreteFactory1 would create Concrete ProductA1 and Concrete ProductB1 (which are designed to work together). ConcreteFactory2 would create Concrete ProductA2 and Concrete ProductB2, and so on.

The main idea is that some concrete ProductA (like a macOS alert) might not work well with just any Concrete ProductB (like a Windows button). So, you don’t let users of your code randomly create different instances. Instead, you give them specific Concrete Factories that create a family of products that are known to work together.

Here’s the updated UML diagram (“create” arrows are shown only for ConcreteFactory1, otherwise it would be a mess):

Real-World Example: UI Themes

Let’s think about a practical use: creating an app with a dark theme and a light theme. Your UI will have elements like menus, lists, buttons, etc. When a user switches themes, all these elements need to change together. For instance, a dark theme usually has white text on a dark background, and a light theme has black text on a light background. Mixing white text with a white background doesn’t make sense!

This is where the Abstract Factory Pattern shines. You’d have:

  • An abstract UIFactory (your general factory). It would have methods like createButton()createList()createLabel(), etc.
  • LightThemeFactory (a concrete factory) that implements the abstract UIFactory. When you ask it to createButton(), it gives you a button suitable for the light theme. All controls from this factory will work together for the light theme.
  • DarkThemeFactory (another concrete factory). When you ask it to createButton(), it gives you a button suitable for the dark theme. All controls from this factory will work together for the dark theme.

Throughout your app, you would use a single factory instance. This instance would either be the Light Theme Factory or the Dark Theme Factory. Whenever you need a UI control, you ask this factory for it. The factory is responsible for giving you the right control and making sure all controls you get from it fit the chosen theme.

This design makes it very easy to add new themes in the future (like colored themes) because all the theme-specific logic is grouped within its dedicated concrete factory.

Key Takeaway

The core difference between the Abstract Factory Pattern and the Factory Method Pattern is that an Abstract Factory has multiple factory methods, allowing it to create multiple related products (a “family” of objects). These products might be of different types (like ProductA and ProductB), or they could even be different versions of the same type (like Button for light theme vs. Button for dark theme) that are designed to be used together.

The Abstract Factory Pattern helps you group specific types of products together inside specific factories, ensuring that these groups of products always make sense when used together. This is something you can’t guarantee with a single factory method.

Simple Factory Pattern vs Factory Method Pattern vs Abstract Factory Pattern

  • Simple Factory Pattern. 1 concrete factory & 1 abstract product
    • SimplePizzaFactory as factory
    • many IPizzas concretions like CheesePizza, VeggiePizza, PepperoniPizza, etc.)
  • Factory Method Pattern. More concrete factories & 1 abstract product:
    • IPizzaStore as abstract factory and NYStylePizzaStore and ChicagoStylePizzaStore as concretions;
    • many IPizza concretions likeNYStyleCheesePizza, ChicagoStyleCheesePizza, NYStyleVeganPizza, ChicagoStyleVeganPizza, etc.)
  • Abstract Factory Pattern. More concrete factories and more products:
    • IPizzaIngredientFactory as abstract factory and NYPizzaIngredientFactory and ChicagoPizzaIngredientFactory as concretions;
    • More abstract products such as Dough, Sauce, Cheese and for each of them there are a lot of concretions (one for each ingredient factory)

Head First Design Patterns: The Factory Pattern

The instantiation of concrete classes through the new operator represents a fundamental tension in object-oriented design. While new is an essential feature of modern object-oriented languages, its use creates dependencies on concrete implementations rather than abstractions. When code references concrete classes directly through instantiation, it becomes tightly coupled to those specific implementations, making the system more fragile and less flexible in the face of change.

Consider a scenario where different concrete classes must be instantiated based on runtime conditions:

Duck duck;
 
if (picnic) {
	duck = new MallardDuck();
} else if (hunting) {
	duck = new DecoyDuck();
} else if (inBathTub) {
	duck = new RubberDuck();
}

A typical implementation might evaluate several conditional branches, each creating a different concrete type. When such instantiation logic appears throughout an application, any modification to the available types requires reopening and examining multiple locations in the codebase. This pattern violates the open-closed principle, which states that software entities should be open for extension but closed for modification.

The solution lies in applying the fundamental design principle of identifying aspects that vary and separating them from what remains stable.

By encapsulating object creation logic into dedicated components, we can isolate the volatility of instantiation decisions from the rest of the application.

The Pizza Store Example

A pizza store implementation illustrates these concepts clearly. The basic ordering workflow consists of creating a pizza, preparing it, baking it, cutting it, and boxing it:

Pizza orderPizza() {
	Pizza pizza = new Pizza();
	
	pizza.prepare();
	pizza.bake();
	pizza.cut();
	pizza.box();
	return pizza;
}

Initially, the orderPizza() method handles both:

  • the instantiation of specific pizza types
  • and the preparation workflow.

When the store begins offering multiple pizza varieties, the method must incorporate conditional logic to instantiate the appropriate concrete pizza class based on the requested type. While the preparation workflow remains stable over time, the selection of available pizza types changes frequently as menu offerings evolve:

Pizza orderPizza(String type) {
	Pizza pizza;
	
	if (type.equals("cheese")) {
		pizza = new CheesePizza();
	} else if (type.equals("greek") {
		pizza = new GreekPizza();
	} else if (type.equals("pepperoni") {
		pizza = new PepperoniPizza();
	}
	
	pizza.prepare();
	pizza.bake();
	pizza.cut();
	pizza.box();
	return pizza;
}

This creates a problematic situation where the orderPizza() method cannot be closed for modification. Each time the pizza selection changes—adding trendy new varieties or discontinuing unpopular ones—the instantiation logic must be modified. The varying instantiation code becomes intermingled with the stable preparation workflow, forcing changes to code that should remain untouched.

Encapsulating Object Creation - Building a simple pizza factory

The Simple Factory idiom addresses this issue by extracting all object creation logic into a dedicated factory class. The SimplePizzaFactory encapsulates the knowledge of which concrete pizza classes exist and the logic for selecting among them. This factory class exposes a createPizza() method that accepts a type parameter and returns the appropriate pizza instance:

public class SimplePizzaFactory {
	public Pizza createPizza(String type) {
		Pizza pizza = null;
		
		if (type.equals("cheese")) {
			pizza = new CheesePizza();
		} else if (type.equals("pepperoni")) {
			pizza = new PepperoniPizza();
		} else if (type.equals("clam")) {
			pizza = new ClamPizza();
		} else if (type.equals("veggie")) {
			pizza = new VeggiePizza();
		}
		return pizza;
	}
}

By consolidating instantiation logic in a single location, the factory ensures that any changes to available pizza types require modifications in only one place.

This encapsulation provides significant advantages when the factory serves multiple clients. Beyond the PizzaStore itself, other components such as menu displays or delivery services might also require pizza creation. By centralizing instantiation in the factory, all these clients benefit from a single point of control for object creation logic.

The Simple Factory can be implemented as a static method, creating what is commonly called a Static Factory. This approach eliminates the need to instantiate the factory object itself, though it sacrifices the ability to subclass the factory and override creation behavior through inheritance.

Reworking the PizzaStore class

The PizzaStore class then becomes a client of the factory. Rather than directly instantiating concrete pizza classes, the orderPizza() method delegates creation to the factory through composition. The store receives a factory reference through its constructor and uses it to obtain pizza instances. This transformation removes all concrete instantiations from the client code, replacing them with calls to the factory’s createPizza() method:

public class PizzaStore {
	SimplePizzaFactory factory;
	
	public PizzaStore(SimplePizzaFactory factory) {
		this.factory = factory;
	}
	public Pizza orderPizza(String type) {
		Pizza pizza;
		
		pizza = factory.createPizza(type);
		
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();
		return pizza;
	}
	// other methods here
}

Notice that we’ve replaced the new operator with a createPizza method in the factory object. No more concrete instantiation here!

The Simple Factory Structure

While the Simple Factory is not formally recognized as a design pattern but rather as a programming idiom, understanding its structure proves valuable. The pattern consists of:

  • a factory class that handles pizza creation
  • a client class that uses the factory
  • the product hierarchy

The factory should be the only part of the application that references concrete pizza classes directly. The client obtains pizza instances through the factory without knowledge of which concrete class is being instantiated. The products themselves implement a common interface, allowing the client to interact with them polymorphically.

Limitations of Simple Factory: Moving Toward a Franchise Model

The Simple Factory approach encounters limitations when expanding to a franchise model with regional variations. Each franchise might want to offer different styles of pizza (New York’s thin crust with light cheese, Chicago’s thick crust with rich sauce and abundant cheese, and California’s distinctive offerings). While creating separate factory instances for each region (NYPizzaFactory, ChicagoPizzaFactory, CaliforniaPizzaFactory) and composing PizzaStore with the appropriate regional factory provides one solution, this approach lacks sufficient quality control:

NYPizzaFactory nyFactory = new NYPizzaFactory();
PizzaStore nyStore = new PizzaStore(nyFactory);
nyStore.orderPizza("Veggie");
 
ChicagoPizzaFactory chicagoFactory = new ChicagoPizzaFactory();
PizzaStore chicagoStore = new PizzaStore(chicagoFactory);
chicagoStore.orderPizza("Veggie");

Franchises might use the factory for pizza creation but diverge from standard procedures during baking, cutting, or packaging. The challenge becomes creating a framework that binds the store and pizza creation together while maintaining flexibility for regional variations. The solution involves moving the createPizza() method back into PizzaStore, but this time as an abstract method. The PizzaStore class itself becomes abstract, defining the complete orderPizza() workflow while declaring createPizza as an abstract method that subclasses must implement:

public abstract class PizzaStore {
 
	public Pizza orderPizza(String type) {
		Pizza pizza;
		
		pizza = createPizza(type);
		
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();
		
		return pizza;
	}
	
	abstract Pizza createPizza(String type);
}

Allowing the subclasses to decide

Each regional franchise then extends PizzaStore with its own concrete subclass. NYPizzaStore, ChicagoPizzaStore, and CaliforniaPizzaStore each provide their own implementation of createPizza(), instantiating the appropriate regional pizza varieties. The createPizza() method in each subclass contains the conditional logic for selecting among that region’s concrete pizza classes, incorporating regional ingredients and styles:

This structure ensures that all franchises inherit the standardized orderPizza() method from the abstract PizzaStore, maintaining consistent preparation procedures across all locations. Meanwhile, each subclass determines which specific pizza types it creates through its createPizza() implementation.

The key insight is that from the perspective of orderPizza(), which is defined in the abstract PizzaStore, the method operates without knowledge of which concrete pizza classes are being instantiated. It invokes the abstract createPizza() method, and the subclass that happens to be executing provides the concrete implementation. The orderPizza() method works with Pizza as an abstraction, calling methods like prepare(), bake(), cut(), and box() without knowing the actual concrete classes involved. This achieves true decoupling between the order workflow and the concrete pizza types.

The determination of which pizza gets made depends on which PizzaStore subclass is being used. When a customer orders from NYPizzaStore, that subclass’s createPizza() method executes, producing New York-style pizzas. The subclass does not make a runtime decision in the traditional sense, but from orderPizza()’s perspective, the subclass determines which concrete pizza type is created. The choice of store dictates which subclass is active, and that subclass’s createPizza() implementation controls the specific pizza instantiation.

Implementing the Pizza Store

The Factory Method pattern manifests through concrete PizzaStore subclasses that implement regional pizza styles. The NYPizzaStore extends the abstract PizzaStore class, inheriting the complete orderPizza() method along with the rest of the standardized functionality. As a subclass, NYPizzaStore must provide a concrete implementation of the abstract createPizza() method, which is where the regional specialization occurs:

public class NYPizzaStore extends PizzaStore {
	Pizza createPizza(String item) {
		if (item.equals("cheese")) {
			return new NYStyleCheesePizza();
		} else if (item.equals("veggie")) {
			return new NYStyleVeggiePizza();
		} else if (item.equals("clam")) {
			return new NYStyleClamPizza();
		} else if (item.equals("pepperoni")) {
			return new NYStylePepperoniPizza();
		} else return null;
	}
}

The createPizza() implementation in NYPizzaStore instantiates New York-style concrete pizza classes. For each pizza type—cheese, pepperoni, clam, or veggie—the method creates the corresponding NY-style variant (NYStyleCheesePizza, NYStylePepperoniPizza, and so forth). The method returns a Pizza reference, but the subclass controls which specific concrete class is instantiated. Importantly, the orderPizza() method in the superclass operates without any knowledge of which concrete Pizza is being created. It simply invokes prepare, bake, cut, and box on the Pizza abstraction, relying on polymorphism to handle the details.

Similarly, ChicagoPizzaStore and CaliforniaPizzaStore implementations follow the same structure, each instantiating their respective regional pizza variants within their createPizza() methods. This design allows the franchise model to scale naturally—each regional store subclasses PizzaStore, provides its region-specific createPizza() implementation, and receives all the standardized ordering functionality without additional effort.

Declaring a factory method

The transformation from the Simple Factory to the Factory Method pattern represents a fundamental shift in responsibility. Rather than delegating object creation to a separate factory object, the PizzaStore class hierarchy now handles instantiation through inheritance. The abstract PizzaStore declares createPizza() as an abstract method, ensuring that all subclasses must provide an implementation. This factory method becomes the focal point where object creation occurs and is encapsulated within the subclass structure:

public abstract class PizzaStore {
 
	public Pizza orderPizza(String type) {
		Pizza pizza;
		
		pizza = createPizza(type);
		
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();
		return pizza;
	}
	protected abstract Pizza createPizza(String type);
	// other methods here
}

The factory method itself serves multiple purposes. As an abstract method, it requires subclasses to handle object creation, establishing a contract that every concrete store must fulfill. The method may accept parameters, such as the pizza type string, allowing it to select among several product variations. Crucially, the factory method isolates the client code—the orderPizza() method and other superclass methods—from knowledge of which concrete product classes are actually being created. The method returns a Product (in this case, Pizza) that the superclass methods can use without understanding the underlying concrete type.

This decoupling between the client code in the superclass and the object creation code in the subclass represents the core value of the Factory Method pattern. The superclass defines the algorithmic structure and workflow, while subclasses inject the specific object creation logic at the appropriate point.

Ordering Pizzas Through the Factory Method

The practical application of this pattern becomes clear when customers order pizzas. Consider two customers with different preferences: one desires New York-style pizza, while another prefers Chicago-style pizza. Each customer must order from the appropriate regional pizza store—the New York customer from NYPizzaStore and the Chicago customer from ChicagoPizzaStore. Both use the same orderPizza() method, but the method produces different pizza styles based on which store subclass is being used. The ordering process follows a clear sequence:

  • The customer obtains an instance of the appropriate PizzaStore subclass. The New York customer instantiates NYPizzaStore, while the Chicago customer instantiates ChicagoPizzaStore.
    PizzaStore nyPizzaStore = new NYPizzaStore();
  • With the store instance in hand, both customers invoke the orderPizza() method with their desired pizza type—cheese, veggie, or another variety.
    nyPizzaStore.orderPizza("cheese");
  • When orderPizza() executes, it calls the createPizza() method to obtain a pizza instance. Because createPizza() is defined differently in each subclass, NYPizzaStore creates New York-style pizzas while ChicagoPizzaStore creates Chicago-style pizzas. The specific concrete class instantiated depends entirely on which subclass’s createPizza() implementation executes. The createPizza() method returns the pizza to orderPizza(), which then proceeds with the standard preparation workflow.
    Pizza pizza = createPizza("cheese");
  • The orderPizza() method operates without any awareness of the concrete pizza type it received. It knows only that it has a Pizza object, and it calls prepare, bake, cut, and box on that abstraction.
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();

This exemplifies the power of programming to interfacesthe superclass workflow remains completely decoupled from the concrete product types.

Implementing the Pizza Products

The Factory Method pattern requires concrete products to complete the system. The product hierarchy begins with an abstract Pizza class from which all concrete pizza implementations derive. Each Pizza maintains a name, dough type, sauce type, and collection of toppings. The abstract class provides default implementations for baking, cutting, and boxing operations, while the preparation method follows a specific sequence of steps that subclasses can customize:

public abstract class Pizza {
	String name;
	String dough;
	String sauce;
	List<String> toppings = new ArrayList<String>();
	
	void prepare() {
		System.out.println("Preparing " + name);
		System.out.println("Tossing dough...");
		System.out.println("Adding sauce...");
		System.out.println("Adding toppings: ");
		for (String topping : toppings) {
			System.out.println(" " + topping);
		}
	}
	
	void bake() {
		System.out.println("Bake for 25 minutes at 350");
	}
	
	void cut() {
		System.out.println("Cutting the pizza into diagonal slices");
	}
	
	void box() {
		System.out.println("Place pizza in official PizzaStore box");
	}
	
	public String getName() {
		return name;
	}
}

Concrete pizza subclasses define their regional characteristics through constructor initialization and method overrides. The NYStyleCheesePizza sets its name to “NY Style Sauce and Cheese Pizza,” specifies thin crust dough and marinara sauce, and adds grated Reggiano cheese as its topping. The ChicagoStyleCheesePizza, by contrast, establishes itself as a “Chicago Style Deep Dish Cheese Pizza” with extra thick crust dough, plum tomato sauce, and shredded mozzarella cheese. Additionally, the Chicago variant overrides the cut method to slice the pizza into squares rather than the default diagonal slices, reflecting regional serving conventions:

}
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 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");
	}
	
	void cut() {
		System.out.println("Cutting the pizza into square slices");
	}
}

Testing the Complete System

A complete test demonstrates the Factory Method pattern in action:

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");
	}
}

The test creates instances of both NYPizzaStore and ChicagoPizzaStore, then orders a cheese pizza from each. When the New York store receives an order, it produces a NY Style Sauce and Cheese Pizza with thin crust and Reggiano cheese, prepared, baked in diagonal slices, and boxed according to the standard procedure. The Chicago store produces a Chicago Style Deep Dish Cheese Pizza with thick crust and mozzarella, cut into squares and similarly boxed. Throughout this process, the superclass orderPizza() method remains completely unaware of the specific pizza details—the subclasses handle all regional variations simply by instantiating the appropriate concrete pizza class.

The Factory Method Pattern

The Factory Method Pattern encapsulates object creation by allowing subclasses to determine which concrete classes to instantiate. All factory patterns fundamentally encapsulate object creation, but the Factory Method achieves this through inheritance rather than composition. The pattern’s structure consists of parallel class hierarchies: the Creator hierarchy and the Product hierarchy.

The abstract Creator class defines an interface containing the factory method for creating objects. This creator typically includes additional methods that operate on the products produced by the factory method. These methods are implemented in the abstract creator without knowledge of which concrete products will actually be created. Only the concrete creator subclasses implement the factory method and produce actual products.

In the pizza store implementation, PizzaStore serves as the abstract creator, defining the abstract createPizza() factory method and implementing the concrete orderPizza() method. The NYPizzaStore and ChicagoPizzaStore concrete creators each implement createPizza() to produce their respective regional pizza styles. Each franchise obtains its own PizzaStore subclass, allowing it to create its own pizza style through its createPizza() implementation.

Parallel Class Hierarchies

The Creator and Product class hierarchies run parallel to each other, with each concrete creator corresponding to a set of related products. The NYPizzaStore encapsulates all knowledge about creating New York-style pizzas, while the ChicagoPizzaStore encapsulates Chicago-style pizza creation knowledge. Both hierarchies feature abstract classes extended by concrete classes that understand specific regional implementations. The factory method serves as the key mechanism for encapsulating this knowledge.

This parallel structure allows the system to scale naturally. Adding a California region requires:

  • creating a CaliforniaPizzaStore concrete creator
  • alongside corresponding California-style concrete product classes.

The two hierarchies extend in tandem, maintaining the separation of concerns between the creation framework and the specific products being created.

Formal Pattern Definition

The Factory Method Pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to its subclasses.

As with every factory, the Factory Method Pattern gives us a way to encapsulate the instantiations of concrete types. The abstract Creator class provides an interface with a factory method for creating objects. Any other methods in the abstract Creator are written to operate on products produced by the factory method, but only subclasses actually implement the factory method and create products.

The phrase “subclasses decide which class to instantiate” reflects not a runtime decision by the subclass itself, but rather the fact that the Creator class is written without knowledge of the actual products that will be created. The decision comes down to which subclass is used to create the product. When code instantiates a NYPizzaStore, that choice determines that New York-style pizzas will be created, but the decision occurred when the client selected which store to instantiate, not through any runtime logic within the subclass.

The pattern structure consists of four key participants:

  • The Product defines the interface for objects the factory method creates.
  • The ConcreteProduct implements the Product interface.
  • The Creator declares the abstract factory method that returns a Product object and may also define a default implementation.
  • The ConcreteCreator overrides the factory method to return an instance of a ConcreteProduct.

All products must implement the same interface so that classes using the products can refer to the interface rather than concrete classes. The ConcreteCreator bears sole responsibility for creating one or more concrete products and is the only class with knowledge of how to create them.

Pattern Advantages and Variations

The Factory Method Pattern provides value even with only one concrete creator because it decouples the product implementation from its use. Adding products or changing a product’s implementation does not affect the Creator, which remains loosely coupled to any ConcreteProduct. The creator can also define a default factory method that produces a concrete product, ensuring that the system can create products even without subclasses.

The pattern supports both parameterized and non-parameterized factory methods. A parameterized factory method can create multiple object types based on a passed parameter, as demonstrated in the pizza store where the type string determines which specific pizza is created. However, factory methods can also produce just one object type without parameters. Both forms constitute valid implementations of the pattern.

The distinction between Simple Factory and Factory Method lies in their flexibility and structure:

  • Simple Factory represents a one-time encapsulation of object creation in a separate factory object that is composed with the client. In the code, SimplePizzaFactory was just a helper class. It’s a “one-shot deal” because once you call it, its job is over. It doesn’t care what you do with the pizza afterward.
  • Factory Method, by contrast, creates a framework where subclasses determine which implementations will be used. In our second code example, the PizzaStore class is the framework:
    • It defines a workflow: order_pizza() calls prepare(), bake(), etc.
    • It doesn’t know which pizza it’s preparing, but it knows the steps.
    • By making create_pizza() abstract, the framework says: “I will handle the business logic, but I’ll let my subclasses decide the specific ingredients.”

The orderPizza method provides a general framework for creating pizzas that relies on a factory method to instantiate the concrete classes. By subclassing PizzaStore, developers decide what concrete products constitute the pizza that orderPizza returns. Simple Factory encapsulates object creation but lacks the flexibility of Factory Method because it provides no mechanism for varying the products being created through inheritance.

The Dependency Inversion Principle

The Dependency Inversion Principle is a formal OO design principle that states: depend upon abstractions, do not depend upon concrete classes.

At first glance, this sounds similar to the familiar advice to “program to an interface, not an implementation,” but it goes further. It asserts that high-level components should not depend on low-level components; rather, both should depend on abstractions.

To understand what this means in practice, consider the DependentPizzaStore example:

public class DependentPizzaStore {
	public Pizza createPizza(String style, String type) {
		Pizza pizza = null;
		if (style.equals("NY")) {
			if (type.equals("cheese")) {
				pizza = new NYStyleCheesePizza();
			} else if (type.equals("veggie")) {
				pizza = new NYStyleVeggiePizza();
			} else if (type.equals("clam")) {
				pizza = new NYStyleClamPizza();
			} else if (type.equals("pepperoni")) {
				pizza = new NYStylePepperoniPizza();
			}
		} else if (style.equals("Chicago")) {
			if (type.equals("cheese")) {
				pizza = new ChicagoStyleCheesePizza();
			} else if (type.equals("veggie")) {
				pizza = new ChicagoStyleVeggiePizza();
			} else if (type.equals("clam")) {
				pizza = new ChicagoStyleClamPizza();
			} else if (type.equals("pepperoni")) {
				pizza = new ChicagoStylePepperoniPizza();
			}
		} else {
			System.out.println("Error: invalid type of pizza");
			return null;
		}
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();
		return pizza;
	}
}

PizzaStore is a high-level component — its behavior is defined entirely in terms of other objects (pizzas). The concrete pizza classes, such as NYStyleCheesePizza or ChicagoStyleVeggiePizza, are the low-level components. In this naïve implementation, PizzaStore’s orderPizza() method instantiates these concrete types directly, creating a hard dependency on every pizza variant. Although a Pizza abstraction exists, it delivers little benefit when concrete classes are still being instantiated throughout the code. Here’s a visual representation too:

Applying the Factory Method Pattern resolves this. By delegating instantiation to a factory, the concrete pizza types are removed from orderPizza() entirely. The result is a design where PizzaStore depends only on the Pizza abstraction — and so do the concrete pizza classes themselves, since they implement (or extend) the Pizza abstract class. Both the high-level and low-level components now point to the same abstraction, rather than the high-level component pointing downward to the low-level ones:

This is precisely where the word “inversion” comes from. In a typical top-down OO design, dependencies flow from high-level classes down to the concrete low-level classes. Applying the Dependency Inversion Principle flips this (look at the diagram above): the low-level components (NYStyleCheesePizza or ChicagoStyleVeggiePizza) now depend on a higher-level abstraction (Pizza), and the high-level component (PizzaStore) ties itself to that same abstraction rather than to the concrete implementations. The dependency arrow, which once pointed straight down, has been inverted.

The practical thinking shift the principle encourages is equally important. Rather than starting the design process at the high-level component and working downward toward concrete types, a developer should start at the low-level objects, identify what can be abstracted, and design the high-level component against that abstraction. For the pizza store, this means recognizing that CheesePizza, VeggiePizza, and ClamPizza are all fundamentally Pizza objects, establishing a Pizza abstraction first, and then designing PizzaStore to depend on that abstraction — with a factory handling the concrete instantiation.

The principle is best understood as a guideline rather than an absolute rule. Virtually every program violates it to some degree — instantiating a String, for example, technically breaks the principle, but String is so stable that it presents no real risk. The principle becomes critical when working with classes that are likely to change, since those are the cases where techniques like Factory Method genuinely protect the design from cascading modifications. The following specific guidelines help avoid violations:

  • No variable should hold a reference to a concrete class. Using new directly produces such a reference; a factory should be used instead.
  • No class should derive from a concrete class. Deriving from a concrete class creates a dependency on it; derivation should always target an interface or abstract class.
  • No method should override an implemented method of a base class. If a method defined in a base class is being overridden, that base class was not truly serving as an abstraction to begin with.

Internalizing these guidelines ensures that, even when they must be violated for pragmatic reasons, the developer makes that choice consciously and deliberately rather than by accident.

The Abstract Factory Pattern

Motivating the Problem: Ingredient Families

With the PizzaStore framework in solid shape, a new problem emerges. While franchises have been following the correct procedures for preparing and baking pizzas, some have begun substituting inferior ingredients to cut costs — a practice that threatens the integrity of the Objectville brand. The solution is to build a centralized factory that produces quality ingredients and ships them to each franchise. The complication is that different regions use different ingredient sets: New York pizzas are made with Marinara sauce, Reggiano cheese, and fresh clams, while Chicago pizzas call for Plum Tomato sauce, Mozzarella, and frozen clams. What we have, in other words, are families of ingredients, where each family shares the same product categories — dough, sauce, cheese, veggies, meats — but provides different regional implementations of each.

Defining the Ingredient Factory Interface

To handle these ingredient families systematically, the first step is to define a PizzaIngredientFactory interface that declares a creation method for each ingredient type:

public interface PizzaIngredientFactory {
    public Dough createDough();
    public Sauce createSauce();
    public Cheese createCheese();
    public Veggies[] createVeggies();
    public Pepperoni createPepperoni();
    public Clams createClam();
}

The plan then proceeds in three steps:

  1. Build a concrete factory for each region by implementing this interface.
  2. Implement a set of ingredient classes (such as ReggianoCheese, ThinCrustDough, and FreshClams) that can be shared across regions where appropriate.
  3. Wire these new ingredient factories into the existing PizzaStore code.

The NYPizzaIngredientFactory, for example, implements each creation method to return the New York-appropriate ingredient — ThinCrustDough, MarinaraSauce, ReggianoCheese, FreshClams, and so on. Chicago’s equivalent factory returns ThickCrustDough, PlumTomatoSauce, MozzarellaCheese, and FrozenClams. One ingredient — SlicedPepperoni — is shared between both regions. Let’s see the implementation of the New York and Chicago ingredient factories:

public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
	public Dough createDough() {
		return new ThinCrustDough();
	}
	
	public Sauce createSauce() {
		return new MarinaraSauce();
	}
	
	public Cheese createCheese() {
		return new ReggianoCheese();
	}
 
	public Veggies[] createVeggies() {
		Veggies veggies[] = { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
	return veggies;
	}
 
	public Pepperoni createPepperoni() {
		return new SlicedPepperoni();
	}
 
	public Clams createClam() {
		return new FreshClams();
	}
}
 
 
public class ChicagoPizzaIngredientFactory implements PizzaIngredientFactory {
	public Dough createDough() {
		return new ThickCrustDough();
	}
	
	public Sauce createSauce() {
		return new PlumTomatoSauce();
	}
	
	public Cheese createCheese() {
		return new MozzarellaCheese();
	}
 
	public Veggies[] createVeggies() {
		Veggies veggies[] = { new Spinach(), new BlackOlives(), new EggPlant()};
	return veggies;
	}
 
	public Pepperoni createPepperoni() {
		return new SlicedPepperoni();
	}
 
	public Clams createClam() {
		return new FrozenClams();
	}
}
 

Reworking the Pizza Classes

With the factories in place, the abstract Pizza class is updated to hold instance variables for each ingredient type (dough, sauce, cheese, veggies, pepperoni, clams), and the prepare() method is made abstract. This is the key change: rather than hard-coding ingredients, prepare() will be responsible for requesting ingredients from the factory.

public abstract class Pizza {
	String name;
	
	Dough dough;
	Sauce sauce;
	Veggies veggies[];
	Cheese cheese;
	Pepperoni pepperoni;
	Clams clam;
 
	abstract void prepare();
 
	void bake() {
		System.out.println("Bake for 25 minutes at 350");
	}
	
	void cut() {
		System.out.println("Cutting the pizza into diagonal slices");
	}
	
	void box() {
		System.out.println("Place pizza in official PizzaStore box");
	}
	
	void setName(String name) {
		this.name = name;
	}
 
	String getName() {
		return name;
	}
	
	public String toString() {
		// code to print pizza here
	}
}

Concrete pizza classes are then dramatically simplified. Previously, there was a separate NYCheesePizza and ChicagoCheesePizza — two classes that differed only in which ingredients they used. With the ingredient factory handling regional differences, a single CheesePizza class suffices for both regions. It receives a PizzaIngredientFactory through its constructor, stores it, and calls it in prepare():

public class CheesePizza extends Pizza {
	PizzaIngredientFactory ingredientFactory;
	
	public CheesePizza(PizzaIngredientFactory ingredientFactory) {
		this.ingredientFactory = ingredientFactory;
	}
 
	void prepare() {
		System.out.println("Preparing " + name);
		dough = ingredientFactory.createDough();
		sauce = ingredientFactory.createSauce();
		cheese = ingredientFactory.createCheese();
	}
}

The pizza class doesn’t know or care which factory it has been given — it simply asks the factory to produce each ingredient and assembles the pizza. This decouples the pizza entirely from the specifics of regional ingredients. The ClamPizza works identically, adding a call to ingredientFactory.createClam() in its prepare() method:

public class ClamPizza extends Pizza {
	PizzaIngredientFactory ingredientFactory;
	
	public ClamPizza(PizzaIngredientFactory ingredientFactory) {
		this.ingredientFactory = ingredientFactory;
	}
	
	void prepare() {
		System.out.println("Preparing " + name);
		dough = ingredientFactory.createDough();
		sauce = ingredientFactory.createSauce();
		cheese = ingredientFactory.createCheese();
		clam = ingredientFactory.createClam();
	}
}

If the factory is a New York one, the clams will be fresh; if it’s Chicago’s, they’ll be frozen — and the pizza class is none the wiser.

Updating the Pizza Stores

The NYPizzaStore is updated to instantiate an NYPizzaIngredientFactory and pass it into each pizza it creates:

public class NYPizzaStore extends PizzaStore {
 
	protected Pizza createPizza(String item) {
		Pizza pizza = null;
		PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
		
		if (item.equals("cheese")) {
			pizza = new CheesePizza(ingredientFactory);
			pizza.setName("New York Style Cheese Pizza");
		} else if (item.equals("veggie")) {
			pizza = new VeggiePizza(ingredientFactory);
			pizza.setName("New York Style Veggie Pizza");
		} else if (item.equals("clam")) {
			pizza = new ClamPizza(ingredientFactory);
			pizza.setName("New York Style Clam Pizza");
		} else if (item.equals("pepperoni")) {
			pizza = new PepperoniPizza(ingredientFactory);
			pizza.setName("New York Style Pepperoni Pizza");
		}
		return pizza;
	}
}

Each pizza type is instantiated with the appropriate factory, ensuring that all ingredients come from the correct regional source. The overall order flow remains the same from the customer’s perspective — a NYPizzaStore is created, orderPizza() is called, createPizza() is invoked internally, a CheesePizza is composed with the New York ingredient factory, and when prepare() is called, the factory produces thin crust dough, marinara sauce, and Reggiano cheese. The rest of the pipeline — baking, cutting, boxing — is unchanged.

The Abstract Factory Pattern Defined

What has been built here is an instance of the Abstract Factory Pattern, formally defined as follows:

The Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.

The structure of the pattern involves four key participants. The AbstractFactory (PizzaIngredientFactory) defines the interface that all concrete factories must implement, consisting of a set of product-creation methods. The ConcreteFactories (NYPizzaIngredientFactory, ChicagoPizzaIngredientFactory) implement that interface and produce a complete family of products specific to their context. The AbstractProducts (Dough, Sauce, Cheese, etc.) define the interfaces for each ingredient type. And the Client (PizzaStore) is written against the abstract factory interface and composed at runtime with an actual concrete factory, meaning it never instantiates product objects directly.

Here’s an UML-like diagram:

Let’s look at it all in terms of our PizzaStore:

The critical benefit is that the client is decoupled from the concrete products it uses. By swapping in a different factory, entirely different product implementations are obtained — marinara instead of plum tomato sauce, fresh instead of frozen clams — without changing a single line of the client code.

Factory Method vs. Abstract Factory

A natural observation is that the creation methods inside an Abstract Factory — createDough(), createSauce(), and so on — each look like a Factory Method. This is not a coincidence. The methods of an Abstract Factory are typically implemented as Factory Methods. Each creation method is declared abstractly in the factory interface and overridden by a concrete subclass to produce a specific product. Factory Methods are simply a natural implementation mechanism for the product methods inside an Abstract Factory.

The two patterns are, however, meaningfully different in intent and mechanism:

  • Factory Method relies on inheritance. Object creation is delegated to subclasses by extending a class and overriding a factory method. The client only knows the abstract type of the product; the concrete subclass decides what to instantiate. The intent is to allow a class to defer instantiation to its subclasses, and it creates one product at a time. Adding a new product type only requires adding a new subclass.
  • Abstract Factory relies on object composition. A factory object is created and passed into the client; the client calls methods on that object to produce products. The intent is to create families of related products while keeping the client decoupled from their concrete types. The trade-off is that adding a new product type requires changing the factory interface itself, which in turn requires updating every concrete factory subclass — a significant cost when the interface must evolve.

Both patterns encapsulate object creation and promote loose coupling by reducing dependence on concrete classes. The choice between them comes down to the situation: use Factory Method when you need to decouple client code from the concrete classes it instantiates, especially when those concrete types are not known ahead of time; use Abstract Factory when you need to ensure that a client works with a coordinated family of products that belong together.

Design Toolbox Summary

The chapter rounds out with a consolidation of the principles and patterns covered. Simple Factory, while not a formal design pattern, provides a straightforward way to centralize object creation and decouple clients from concrete classes. Factory Method encapsulates object creation through inheritance, deferring instantiation to subclasses. Abstract Factory encapsulates the creation of entire product families through object composition. All three promote loose coupling by reducing application-level dependencies on concrete types. Underlying all of them is the Dependency Inversion Principle: avoid dependencies on concrete types, and strive always to depend on abstractions. Factories are among the most powerful techniques available for honoring that principle in practice.