Factory Pattern
The problem
When call sites instantiate concrete classes directly, they accumulate knowledge they should not have. A module that calls new EmailNotification(address) now knows that email notifications exist, how to construct one, and that the constructor takes an address. If you add SMS support, every call site changes. If you want to test with a fake notification, you have to modify the code under test or reach for a mocking library.
The Factory pattern centralizes object creation behind a function or method. Call sites ask for “a notification” and receive one, without knowing the concrete type. Adding a new notification channel means adding a case to the factory, not touching the code that uses notifications. Test code can provide a factory that returns a spy. The pattern comes in two forms covered here: a simple factory function (not from the GoF, but the most common form in practice) and the GoF Factory Method pattern, where subclasses override a protected creation method.
Structure
classDiagram class Notification { <<interface>> +send(message) } class EmailNotification { -address: string +send(message) } class SMSNotification { -phone: string +send(message) } class PushNotification { -deviceId: string +send(message) } class NotificationService { <<abstract>> +createNotification(dest)* Notification +notify(dest, message) } class EmailService { +createNotification(dest) Notification } Notification <|-- EmailNotification Notification <|-- SMSNotification Notification <|-- PushNotification NotificationService <|-- EmailService NotificationService --> Notification : createsWhen to use
- Call sites should not depend on a specific concrete class; they only need the interface.
- You want to swap implementations at runtime or inject test doubles without changing the consuming code.
- A family of related classes shares a creation pattern and you want one authoritative place for that logic.
- You need extensibility: new types should be addable without touching existing call sites.
Implementation
Each example shows a simple factory function first (the common case), then the GoF Factory Method pattern where an abstract creator delegates construction to subclasses. Abstract Factory is not shown in full because it adds complexity without new concepts; use it when you need to produce families of related objects together. Python uses abc.ABC for both the Notification interface and the abstract service, and the simple factory uses match (Python 3.10+; use a dict mapping for older versions). Go has no abstract classes, so Factory Method maps to an interface with a Create method injected as a struct field.
Click Run TS to execute. First run downloads Babel (~400 KB, cached after that).
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).
Click Run Go to execute. Runs via the Go Playground API.
Tradeoffs
| Pro | Con |
|---|---|
| Callers depend on abstractions, not concrete types | Adds a layer of indirection |
| New types added without changing call sites | Simple factory function does not enforce the open-closed principle |
| Enables dependency injection and test doubles | Factory Method requires subclassing, which can proliferate classes |
| Centralizes construction logic | Abstract Factory adds another level and can become complex |
Gotchas
- Simple factory vs. Factory Method: a simple factory function is not a GoF pattern, but it is often what you actually need. Reach for Factory Method only when you need extensibility through subclassing. Starting with a function and graduating to a class is the right order.
- Python
matchrequires 3.10+: for older Python, a dict mapping strings to constructors ({'email': EmailNotification, 'sms': SMSNotification}) is readable and fast. Avoid longif/elifchains. - Go and abstract classes: Go has no abstract classes. Simulate Factory Method with an interface that carries a
Createmethod, then inject the factory as a field. This also makes it trivial to inject a fake factory in tests. - Abstract Factory for families: Abstract Factory (a factory that produces multiple related objects that must be used together) is a separate pattern. If you only have one product type, Factory Method is enough. Add Abstract Factory when you have a coherent set of products that vary together (e.g. a UI theme that produces buttons, inputs, and modals all in the same style).
- Factory functions and exhaustiveness: TypeScript’s discriminated union with
switchgives exhaustiveness checking. Python’smatchwith acase _fallthrough gives a runtime error on unknown types. Go’sswitchwith adefault: panicdoes the same. All three are correct. The danger is a factory that silently returnsnilon an unknown type.
References
- Design Patterns: Elements of Reusable Object-Oriented Software, GoF, pp. 107-116 (Factory Method) and pp. 87-95 (Abstract Factory)
- Factory Method Pattern, Refactoring Guru, worked examples in multiple languages
- Abstract Factory Pattern, Refactoring Guru, when Factory Method is not enough
- Factory Functions in JavaScript, Eric Elliott, the functional approach to object creation without classes
Related topics
- Design Patterns, the full GoF catalog and pattern index
- Builder, another creational pattern for multi-step construction
- Strategy, often paired with factories to inject different algorithm implementations