Strategy vs Factory Pattern

February 23, 2024

Overview

The Strategy and Factory design patterns are distinct and intended for different use cases. One is intended to polymorphically alter behavior of an existing object, while the other is intended for variations in object creation.

Understanding polymorphism is a prerequisite to understanding many design patterns, including Strategy and Factory. When learning these design patterns, it's important to take every example with a grain of salt. These patterns can be molded and modified to fit different use cases. Software is very malleable, after all (compared to concrete and steel post-construction in the real world).

  • Strategy Pattern: Uses an interface as a contract for creating multiple concrete implementations that can be swapped in an object at runtime to produce different behavior.
  • Factory Pattern: Uses an interface as a contract for creating multiple concrete implementations that are instantiated by an object that takes the role of an Object Factory (although a variation does exist where one Factory class is responsible for creating each type -- see below).

Both of these patterns are detailed in the book Head First Design Patterns.

At first glance, these two patterns appear to be very distinct. As we look closer, we can indeed find overlap that is discussed in the Conclusion. See if you can find a hint along the way.

Notes

  • Concrete Implementation: Refers to a class that can be instantiated, rather than an interface that it implements or an abstract class that it extends.
  • Class vs Interface: Learn more about the difference at Class vs Interface.

Table of Contents

Comparison
Strategy Pattern
Factory Pattern
Conclusion

Comparison
^

StrategyFactory
PurposeDefine a family of interchangeable logic with minimal overhead to be swappable at runtime. Decouples the logic from the entity that uses it.Define a class (or group of classes) responsible for creating objects that match an interface. This class (or group of classes) is considered to be a Factory.
Use CaseWhen the choice for how to do a task (or set of tasks) depends on something that can only be determined at runtime.When the choice for how to do a set of tasks (defined in a class) depends on something that can only be determined at runtime.
ExamplesSorting and navigation algorithms, payment methods.Different file readers+writers, or third-party connections based on user selection.

Strategy Pattern
^

Given the flexibility and malleability of software, it's easy for a pattern (e.g. Strategy) to be modified to a point where it's difficult to tell whether we're still looking at Strategy pattern, or something entirely different. The key element that distinguishes the Strategy pattern is the mechanism to swap the concrete implementation used by a method of some other class at runtime. See the below example:

// A parent class that is sub-classed can also be used instead of an interface in Strategy pattern. 
// Its distinction is the utilization of polymorphism to swap the type used by a method at runtime. 
// In this example, we're using Person.setPet() to swap types at runtime. 
abstract class Dog { 
    public String name;
    public Dog(String name) {
        this.name = name;
    }
    // Keeping it simple with just this one `getName` method. 
    String getName() { 
        return this.name;
    }
}

class Samoyed extends Dog {
    public Samoyed(String name) {
        super(name);
    }
}

class JackRussellTerrier extends Dog {
    public JackRussellTerrier(String name) {
        super(name);
    }
} 

class Person {
    Dog currentPet;
    
    public Person(Dog pet) {
        currentPet = pet;
    }
    
    public void goForWalk() {
        System.out.println("Going for a walk with " + this.currentPet.getName() + ".");
    }
    
    public void setPet(Dog pet) {
        this.currentPet = pet;
    }
}

class RunInJShell {
    public static void example() {
        Dog samoyed = new Samoyed("Rupert");
        
        Person sam = new Person(samoyed);
        sam.goForWalk();  // Prints: "Going for a walk with Rupert.";
        
        sam.setPet(new JackRussellTerrier("Jack"));
        sam.goForWalk();  // Prints: "Going for a walk with Jack.";
    }
}

Run the above example by opening jshell, pasting the above code, and entering: RunInJShell.example();. This example was created by hand because dogs are the best (light-hearted non-sequitur... could model every example around dogs).

This setter + member variable technique of swapping a concrete implementation of a class to change its behavior is just one of several. Some others are:

  • Setter + HashMap (or Dictionary): This is a very similar mechanism to the above example, except the various concrete implementations would be stored in a HashMap (or Dictionary in a language like Python). The setter would change the key that corresponds to the value (the instantiated type).
  • Method Injection: The concrete implementation could be passed in as an argument of the method being called (getName() in our example above).
  • Using a Factory: This technique would abstract away the type selection logic into a Factory class. That Factory class may select the type based on any number of conditions at runtime (e.g. via some external periodic request to set state).

Factory Pattern
^

There are several variations of the Factory Pattern, but they all share a common goal of creating objects that share an implementation. This technique encapsulates and abstracts the entire instantiation behavior of a type, rather than just a method (like the Strategy pattern). Below is a simple example where object creation is decided via a method parameter:

// Define the Product interface
interface Product {
    void use();
}

// Define ConcreteProductA class
class ConcreteProductA implements Product {
    @Override
    public void use() {
        System.out.println("Using ConcreteProductA");
    }
}

// Define ConcreteProductB class
class ConcreteProductB implements Product {
    @Override
    public void use() {
        System.out.println("Using ConcreteProductB");
    }
}

// Define ProductFactory class with a method to create products
class ProductFactory {
    public enum ProductType {
        PRODUCT_A, PRODUCT_B
    }
    
    public static Product createProduct(ProductType type) {
        switch (type) {
            case PRODUCT_A: return new ConcreteProductA();
            case PRODUCT_B: return new ConcreteProductB();
            default: throw new IllegalArgumentException("Unknown product type");
        }
    }
}

// Define a static method to run the factory demo
public static void runFactoryDemo() {
    Product productA = ProductFactory.createProduct(ProductFactory.ProductType.PRODUCT_A);
    productA.use(); // Using ConcreteProductA
    
    Product productB = ProductFactory.createProduct(ProductFactory.ProductType.PRODUCT_B);
    productB.use(); // Using ConcreteProductB
}

// Execute the demo
runFactoryDemo();

Above example created by ChatGPT by OpenAI.

Other variations of this pattern exist: All variations of the Factory Design Pattern.

An important distinction in the Factory Pattern is that its focus is on object creation. This means that it can hold potentially complex logic that is required for the instantiation of objects.

Conclusion
^

Is there overlap between these two patterns? There can be. If you look at the Comparison table above, the Use Case category defines an overlap. In our Strategy code example, only a single method's behavior is changed by swapping the member variable. However, it's possible for more than a single member variable's behavior to change. In fact, the behavior of an entire class can be changed by swapping a single member variable. Similarly, the Factory Pattern is concerned with creating objects of a single family. Each different object that is created by the Factory Pattern produces an entire class of different behavior. This is the overlap.