Skip to main content

Factory Method

complexitypopularity

Let a superclass define how a product is used while subclasses decide which concrete product gets created.

The problem

Version one of your logistics app moves cargo by truck, so Truck is everywhere. Then the sea freight companies come knocking, and it turns out most of your codebase has quietly memorized the word Truck. Adding Ship means editing all of it. Add rail after that and you do the whole tour again.

The result is code marbled with conditionals that switch behavior based on the class of the transport object. Every new carrier widens every branch.

The solution

Stop calling new Truck() in the middle of your business logic. Replace direct construction with a call to a factory method. The new operator still runs, it just runs inside that one method, and the objects it returns are called products.

The payoff arrives when a subclass overrides the factory method and returns a different product. RoadLogistics.createTransport() returns a truck, SeaLogistics.createTransport() returns a ship, and planDelivery() in the base class never notices. One requirement makes this work: all products implement a common interface, and the factory method's declared return type is that interface.

ApplicationLogistics (abstract)SeaLogisticsShip (product)new SeaLogistics()1planDelivery()2createTransport()3new Ship()4returns Transport5deliver()6
  1. 1The application reads a config and picks a creator subclass. This is the only place a concrete choice is made.
  2. 2From here on the client talks to the base type. It sees Logistics, not SeaLogistics.
  3. 3The business logic calls its own factory method. Polymorphism routes the call to the subclass override.
  4. 4The subclass is the only code in the building that knows the word "Ship".
  5. 5It comes back typed as the interface, so the base class stays blissfully ignorant of what it received.
  6. 6The shared logic runs against the interface. Same method call would have moved a truck.

Structure

Four moving parts: the product interface, the concrete products, the creator that declares the factory method, and the concrete creators that override it. Declare the base factory method abstract to force every subclass to answer the question, or give it a body to provide a sensible default product.

«interface»Transportdeliver()PRODUCT«abstract»LogisticscreateTransport(): TransportplanDelivery()CREATORTruckdeliver()CONCRETEShipdeliver()CONCRETERoadLogisticscreateTransport(): TransportCONCRETESeaLogisticscreateTransport(): TransportCONCRETE
implementsextendsusescreates

Code

Same example three ways: a logistics planner that works with trucks or ships without ever naming one.

// The logistics app grew a Truck-shaped hole in its heart.
class Logistics is
method planDelivery() is
// Direct construction welds this class to Truck forever.
Truck t = new Truck()
t.deliver()
 
// Sea shipping arrives, and so does the conditional rot:
class Logistics is
method planDelivery(mode) is
if (mode == "road") then
Truck t = new Truck()
t.deliver()
else if (mode == "sea") then
Ship s = new Ship()
s.deliver()
// Rail next quarter? Add another branch. And another. Forever.
// Business logic, written once, agnostic about the cargo carrier.
abstract class Logistics is
abstract method createTransport(): Transport
 
method planDelivery() is
Transport t = this.createTransport()
t.deliver()
 
// Supporting a new carrier is an additive, three-line affair:
class AirLogistics extends Logistics is
method createTransport(): Transport is
return new Plane()
 
// Nothing above AirLogistics had to change. That is Open/Closed with receipts.
// The creator declares the factory method. Note that shipping
// cargo, not manufacturing transport, is its actual day job.
abstract class Logistics is
// Subclasses supply the implementation. It may also have a
// default body that returns some standard product.
abstract method createTransport(): Transport
 
// The business logic lives here and never names a concrete class.
method planDelivery() is
Transport t = this.createTransport()
t.deliver()
 
// Concrete creators override exactly one method.
class RoadLogistics extends Logistics is
method createTransport(): Transport is
return new Truck()
 
class SeaLogistics extends Logistics is
method createTransport(): Transport is
return new Ship()
 
// Every product speaks the same language.
interface Transport is
method deliver()
 
class Truck implements Transport is
method deliver() is
// Drive the cargo down a road in a box.
 
class Ship implements Transport is
method deliver() is
// Float the cargo across a sea in a container.
 
// The client picks a creator once, then forgets about types.
class Application is
field logistics: Logistics
 
method initialize() is
config = readApplicationConfigFile()
if (config.mode == "road") then
logistics = new RoadLogistics()
else if (config.mode == "sea") then
logistics = new SeaLogistics()
else
throw new Exception("Unknown delivery mode.")
 
method main() is
this.initialize()
logistics.planDelivery()

When to use it

  • You do not know upfront the exact types your code will need to instantiate. New product types then arrive as a new creator subclass rather than an edit to existing code.
  • You ship a library or framework and want users to extend its internals. They subclass the component and override the factory method that builds it, and the framework picks up their version.
  • You want to reuse expensive objects such as connections or file handles. A constructor must return something new; a factory method may hand back a pooled instance.

Pitfalls

  • Subclass explosion. One creator subclass per product adds up. If the products are numerous and boring, a parameterized factory method beats a dozen near-empty classes.
  • A giant switch in the base method. During refactoring, the factory method often grows a switch over a control parameter. That is a legitimate way station, not a destination.
  • Forgetting the return type. Declare the factory method as returning the concrete class and the coupling you just removed walks straight back in.

Don't confuse it with

  • Abstract Factory. Many designs start with Factory Method because it is simple and subclass-customizable, then evolve toward Abstract Factory when one product stops being enough. Keep bolting factory methods onto one creator and you have essentially arrived.
  • Prototype. Prototype avoids inheritance entirely by cloning a configured instance, at the price of a fussier initialization step. Factory Method leans on inheritance but needs no such setup.
  • Template Method. Factory Method is a specialization of Template Method where the overridable step happens to return an object. It can equally serve as one step inside a larger template.

Check yourself

Question 1 of 5

What has to be true about the objects a factory method returns?