Creating an object without naming its class. Three different things share the word "factory", and interviewers expect you to keep them apart.
An order ships, and the app tells the customer. Some customers get an email, some an SMS, some a push notification, and which one is a setting. Somewhere in the code a string like "sms" has to turn into an object that can send an SMS. That step, turning a name or a setting into the right object, is what the factory patterns are about.
It is also where a lot of code quietly goes wrong. The service that sends the message ends up knowing every channel class, so every new channel edits it. And at a checkout that serves two countries, tax, payment and invoice rules have to come from the same country: a German tax rule next to an Indian invoice number is a bug no compiler catches. This lesson starts from the if-ladder most people write first, then builds up three tools: a registry of constructors, Factory Method and Abstract Factory. The case studies use the first of the three, in the parking lot (build_lot) and the rate limiter (FACTORIES, filled from config).
A food court cashier who cooks every dish needs every recipe. Give each cuisine its own counter, and a combo comes out matching.
The first version: the service builds its own channel#
The first version has two channels, and the service that confirms an order picks one with an if:
# factory_v1.py: the confirmation service picks and builds its own channel
class EmailChannel:
def send(self, to: str, text: str) -> str:
return f"email to {to}: {text}"
class SmsChannel:
def send(self, to: str, text: str) -> str:
return f"sms to {to}: {text[:20]}" # an SMS is cut at 20 characters here
class ConfirmationService:
def confirm(self, channel_name: str, to: str, order_id: int) -> str:
if channel_name == "email": # the service names every concrete class
channel = EmailChannel()
elif channel_name == "sms":
channel = SmsChannel()
else:
raise ValueError(f"unknown channel: {channel_name}")
return channel.send(to, f"Order {order_id} shipped, arriving Friday")
service = ConfirmationService()
print(service.confirm("email", "asha@mail.test", 41))
print(service.confirm("sms", "98765 43210", 41))
try:
service.confirm("fax", "none", 41)
except ValueError as e:
print("error:", e)// FactoryV1.java: the confirmation service picks and builds its own channel
class FactoryV1 {
interface Channel { String send(String to, String text); }
static final class EmailChannel implements Channel {
public String send(String to, String text) { return "email to " + to + ": " + text; }
}
static final class SmsChannel implements Channel {
public String send(String to, String text) {
return "sms to " + to + ": " + text.substring(0, Math.min(20, text.length())); // an SMS is cut at 20 characters here
}
}
static final class ConfirmationService {
String confirm(String channelName, String to, int orderId) {
Channel channel;
if (channelName.equals("email")) channel = new EmailChannel(); // the service names every concrete class
else if (channelName.equals("sms")) channel = new SmsChannel();
else throw new IllegalArgumentException("unknown channel: " + channelName);
return channel.send(to, "Order " + orderId + " shipped, arriving Friday");
}
}
public static void main(String[] args) {
ConfirmationService service = new ConfirmationService();
System.out.println(service.confirm("email", "asha@mail.test", 41));
System.out.println(service.confirm("sms", "98765 43210", 41));
try {
service.confirm("fax", "none", 41);
} catch (IllegalArgumentException e) {
System.out.println("error: " + e.getMessage());
}
}
}// factory_v1.cpp: the confirmation service picks and builds its own channel
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
struct Channel {
virtual ~Channel() = default;
virtual std::string send(const std::string& to, const std::string& text) const = 0;
};
struct EmailChannel : Channel {
std::string send(const std::string& to, const std::string& text) const override { return "email to " + to + ": " + text; }
};
struct SmsChannel : Channel {
std::string send(const std::string& to, const std::string& text) const override {
return "sms to " + to + ": " + text.substr(0, 20); // an SMS is cut at 20 characters here
}
};
struct ConfirmationService {
std::string confirm(const std::string& channel_name, const std::string& to, int order_id) const {
std::unique_ptr<Channel> channel;
if (channel_name == "email") channel = std::make_unique<EmailChannel>(); // the service names every concrete class
else if (channel_name == "sms") channel = std::make_unique<SmsChannel>();
else throw std::invalid_argument("unknown channel: " + channel_name);
return channel->send(to, "Order " + std::to_string(order_id) + " shipped, arriving Friday");
}
};
int main() {
ConfirmationService service;
std::cout << service.confirm("email", "asha@mail.test", 41) << "\n";
std::cout << service.confirm("sms", "98765 43210", 41) << "\n";
try {
service.confirm("fax", "none", 41);
} catch (const std::invalid_argument& e) {
std::cout << "error: " << e.what() << "\n";
}
}// factoryV1.js: the confirmation service picks and builds its own channel
class EmailChannel {
send(to, text) { return `email to ${to}: ${text}`; }
}
class SmsChannel {
send(to, text) { return `sms to ${to}: ${text.slice(0, 20)}`; } // an SMS is cut at 20 characters here
}
class ConfirmationService {
confirm(channelName, to, orderId) {
let channel;
if (channelName === "email") channel = new EmailChannel(); // the service names every concrete class
else if (channelName === "sms") channel = new SmsChannel();
else throw new Error(`unknown channel: ${channelName}`);
return channel.send(to, `Order ${orderId} shipped, arriving Friday`);
}
}
const service = new ConfirmationService();
console.log(service.confirm("email", "asha@mail.test", 41));
console.log(service.confirm("sms", "98765 43210", 41));
try {
service.confirm("fax", "none", 41);
} catch (e) {
console.log("error:", e.message);
}email to asha@mail.test: Order 41 shipped, arriving Friday
sms to 98765 43210: Order 41 shipped, ar
error: unknown channel: faxIt works, and for two channels it is fine. The SMS line shows the 20-character cut: "Order 41 shipped, arriving Friday" is 33 characters, and the first 20 are "Order 41 shipped, ar". The trouble is what happens next, and none of it is about these 20 lines:
- The service knows every channel class. Add
pushand you editconfirm, then re-test the order logic that was working. - The choice gets copied. The same "which class for this name?" question comes up in a preview screen, a retry job and a settings page. Each one grows its own ladder, and they drift apart.
- No way to substitute. A test of
confirmcannot swap in a fake channel, so it sends real messages or never runs. - Typos are found late.
"smss"in a settings file is found when the first customer is notified, not at start-up.
Which part changes?
The product team wants a push channel next month and whatsapp the month after. In the first version, which part of the code do you edit each time, and which part should never need to change?
One strong answer
The choice of class changes; what the service does with the channel does not. Today both live inside confirm, so every new channel edits a method that is really about order confirmations. Pull "which class do I create for this name?" out into one place, and confirm only calls channel.send(...) on whatever it is given.
The pattern#
Before the book's definitions, a warning: three different things get called "a factory", and mixing them up is the most common mistake in interviews.
| Name | What it is | In the book? |
|---|---|---|
| Simple factory (a factory function, or a registry of constructors) | One function or one table that turns a name or setting into an object | No. It is a useful idiom, not one of the 23 patterns |
| Factory Method | A method that a base class calls to get a product, and that subclasses override to decide which class | Yes |
| Abstract Factory | An interface with several create methods that return a family of related objects | Yes |
The Design Patterns book (1994, Gamma, Helm, Johnson and Vlissides) states the intent of Factory Method as: "Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses." And of Abstract Factory: "Provide an interface for creating families of related or dependent objects without specifying their concrete classes."
One more name to keep apart: a static factory method such as Java's List.of(...) or Integer.valueOf(...) (Joshua Bloch's first item in Effective Java) is just a named constructor. It is not the book's Factory Method, because nothing is overridden.
| Role | Factory Method | Abstract Factory |
|---|---|---|
| Product | Channel: what gets created |
Payment, TaxRule, Invoice |
| Creator / Factory | ConfirmationJob, whose run() calls create_channel() |
CheckoutKit, with create_payment(), create_tax(), create_invoice() |
| Concrete creator / factory | EmailJob, SmsJob: each overrides the one method |
IndiaKit, GermanyKit: each builds one matching family |
| Client | run() itself: it uses the product and never names its class |
checkout(kit, ...): it only ever sees the interfaces |
%% title: Factory Method: a subclass chooses the product
classDiagram
class Channel {
<<interface>>
+send(to, text) str
}
class EmailChannel
class SmsChannel
class ConfirmationJob {
<<abstract>>
+run(to, order_id) str
+create_channel() Channel
}
class EmailJob
class SmsJob
Channel <|.. EmailChannel
Channel <|.. SmsChannel
ConfirmationJob <|-- EmailJob
ConfirmationJob <|-- SmsJob
ConfirmationJob ..> Channel : calls send
EmailJob ..> EmailChannel : creates
SmsJob ..> SmsChannel : creates%% title: Abstract Factory: one kit per country, three matching products
classDiagram
class CheckoutKit {
<<interface>>
+create_payment() Payment
+create_tax() TaxRule
+create_invoice() Invoice
}
class IndiaKit
class GermanyKit
class Payment {
<<interface>>
}
class TaxRule {
<<interface>>
}
class Invoice {
<<interface>>
}
CheckoutKit <|.. IndiaKit
CheckoutKit <|.. GermanyKit
IndiaKit ..> Payment : creates the India one
IndiaKit ..> TaxRule : creates the India one
IndiaKit ..> Invoice : creates the India one
GermanyKit ..> Payment : creates the Germany one
GermanyKit ..> TaxRule : creates the Germany one
GermanyKit ..> Invoice : creates the Germany oneRead the diagrams. In the first, ConfirmationJob.run() is the fixed part: it builds the text, asks create_channel() for a channel and sends. The subclasses override only that one method, and each creates its own channel class. The dashed creates arrows are the only places a concrete channel is named. In the second, the client holds a CheckoutKit and only ever sees the three product interfaces. IndiaKit and GermanyKit each create a matching set, so the client cannot end up with Indian tax and a German invoice, because it never picks them one at a time.
Three different things are called a factory: a simple factory, Factory Method and Abstract Factory. Interviewers expect you to keep them apart.
The code: a registry of constructors#
Before either pattern, the smallest fix. Keep the "name to class" knowledge in one table, and hand the service the table:
# channel_registry.py: a registry of constructors replaces the if-ladder
from typing import Callable, Dict, Protocol
class Channel(Protocol): # the interface every channel meets: send(to, text)
def send(self, to: str, text: str) -> str: ...
class EmailChannel:
def send(self, to: str, text: str) -> str:
return f"email to {to}: {text}"
class SmsChannel:
def send(self, to: str, text: str) -> str:
return f"sms to {to}: {text[:20]}" # an SMS is cut at 20 characters here
class PushChannel: # added later: nothing below this class is edited for it, except one registry line in main
def send(self, to: str, text: str) -> str:
return f"push to {to}: {text}"
class ChannelRegistry:
def __init__(self) -> None:
self._makers: Dict[str, Callable[[], Channel]] = {}
def register(self, name: str, maker: Callable[[], Channel]) -> None:
self._makers[name] = maker
def create(self, name: str) -> Channel:
if name not in self._makers:
raise ValueError(f"unknown channel: {name}")
return self._makers[name]()
class ConfirmationService:
def __init__(self, registry: ChannelRegistry) -> None: # handed the registry: it names no concrete channel
self.registry = registry
def confirm(self, channel_name: str, to: str, order_id: int) -> str:
return self.registry.create(channel_name).send(to, f"Order {order_id} shipped, arriving Friday")
registry = ChannelRegistry()
registry.register("email", EmailChannel) # a class is already a zero-argument factory in Python
registry.register("sms", SmsChannel)
service = ConfirmationService(registry)
config = {"channel": "sms"} # as if read from a settings file
print(service.confirm("email", "asha@mail.test", 41))
print("from config:", service.confirm(config["channel"], "98765 43210", 41))
registry.register("push", PushChannel) # the whole change for a new channel: one class and this line
print(service.confirm("push", "asha-phone", 41))
try:
service.confirm("fax", "none", 41)
except ValueError as e:
print("error:", e)// ChannelRegistry.java: a registry of constructors replaces the if-ladder
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
class ChannelRegistryDemo {
interface Channel { String send(String to, String text); }
static final class EmailChannel implements Channel {
public String send(String to, String text) { return "email to " + to + ": " + text; }
}
static final class SmsChannel implements Channel {
public String send(String to, String text) {
return "sms to " + to + ": " + text.substring(0, Math.min(20, text.length())); // an SMS is cut at 20 characters here
}
}
static final class PushChannel implements Channel { // added later: nothing below this class is edited for it, except one registry line in main
public String send(String to, String text) { return "push to " + to + ": " + text; }
}
static final class ChannelRegistry {
private final Map<String, Supplier<Channel>> makers = new HashMap<>();
void register(String name, Supplier<Channel> maker) { makers.put(name, maker); }
Channel create(String name) {
Supplier<Channel> maker = makers.get(name);
if (maker == null) throw new IllegalArgumentException("unknown channel: " + name);
return maker.get();
}
}
static final class ConfirmationService {
private final ChannelRegistry registry;
ConfirmationService(ChannelRegistry registry) { this.registry = registry; } // handed the registry: it names no concrete channel
String confirm(String channelName, String to, int orderId) {
return registry.create(channelName).send(to, "Order " + orderId + " shipped, arriving Friday");
}
}
public static void main(String[] args) {
ChannelRegistry registry = new ChannelRegistry();
registry.register("email", EmailChannel::new); // a constructor reference is a Supplier
registry.register("sms", SmsChannel::new);
ConfirmationService service = new ConfirmationService(registry);
Map<String, String> config = Map.of("channel", "sms"); // as if read from a settings file
System.out.println(service.confirm("email", "asha@mail.test", 41));
System.out.println("from config: " + service.confirm(config.get("channel"), "98765 43210", 41));
registry.register("push", PushChannel::new); // the whole change for a new channel: one class and this line
System.out.println(service.confirm("push", "asha-phone", 41));
try {
service.confirm("fax", "none", 41);
} catch (IllegalArgumentException e) {
System.out.println("error: " + e.getMessage());
}
}
}// channel_registry.cpp: a registry of constructors replaces the if-ladder
#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <stdexcept>
#include <string>
#include <utility>
struct Channel {
virtual ~Channel() = default;
virtual std::string send(const std::string& to, const std::string& text) const = 0;
};
struct EmailChannel : Channel {
std::string send(const std::string& to, const std::string& text) const override { return "email to " + to + ": " + text; }
};
struct SmsChannel : Channel {
std::string send(const std::string& to, const std::string& text) const override {
return "sms to " + to + ": " + text.substr(0, 20); // an SMS is cut at 20 characters here
}
};
struct PushChannel : Channel { // added later: nothing below this class is edited for it, except one registry line in main
std::string send(const std::string& to, const std::string& text) const override { return "push to " + to + ": " + text; }
};
using Maker = std::function<std::unique_ptr<Channel>()>;
class ChannelRegistry {
std::map<std::string, Maker> makers_;
public:
void add(const std::string& name, Maker maker) { makers_[name] = std::move(maker); }
std::unique_ptr<Channel> create(const std::string& name) const {
auto it = makers_.find(name);
if (it == makers_.end()) throw std::invalid_argument("unknown channel: " + name);
return it->second();
}
};
class ConfirmationService {
const ChannelRegistry& registry_; // handed the registry: it names no concrete channel
public:
explicit ConfirmationService(const ChannelRegistry& registry) : registry_(registry) {}
std::string confirm(const std::string& channel_name, const std::string& to, int order_id) const {
return registry_.create(channel_name)->send(to, "Order " + std::to_string(order_id) + " shipped, arriving Friday");
}
};
int main() {
ChannelRegistry registry;
registry.add("email", [] { return std::make_unique<EmailChannel>(); });
registry.add("sms", [] { return std::make_unique<SmsChannel>(); });
ConfirmationService service(registry);
std::map<std::string, std::string> config{{"channel", "sms"}}; // as if read from a settings file
std::cout << service.confirm("email", "asha@mail.test", 41) << "\n";
std::cout << "from config: " << service.confirm(config.at("channel"), "98765 43210", 41) << "\n";
registry.add("push", [] { return std::make_unique<PushChannel>(); }); // the whole change for a new channel: one class and this line
std::cout << service.confirm("push", "asha-phone", 41) << "\n";
try {
service.confirm("fax", "none", 41);
} catch (const std::invalid_argument& e) {
std::cout << "error: " << e.what() << "\n";
}
}// channelRegistry.js: a registry of constructors replaces the if-ladder
class EmailChannel {
send(to, text) { return `email to ${to}: ${text}`; }
}
class SmsChannel {
send(to, text) { return `sms to ${to}: ${text.slice(0, 20)}`; } // an SMS is cut at 20 characters here
}
class PushChannel { // added later: nothing below this class is edited for it, except one registry line
send(to, text) { return `push to ${to}: ${text}`; }
}
class ChannelRegistry {
#makers = new Map();
register(name, maker) { this.#makers.set(name, maker); }
create(name) {
const maker = this.#makers.get(name);
if (maker === undefined) throw new Error(`unknown channel: ${name}`);
return maker();
}
}
class ConfirmationService {
constructor(registry) { this.registry = registry; } // handed the registry: it names no concrete channel
confirm(channelName, to, orderId) {
return this.registry.create(channelName).send(to, `Order ${orderId} shipped, arriving Friday`);
}
}
const registry = new ChannelRegistry();
registry.register("email", () => new EmailChannel()); // a class cannot be called without new, so wrap it in a function
registry.register("sms", () => new SmsChannel());
const service = new ConfirmationService(registry);
const config = { channel: "sms" }; // as if read from a settings file
console.log(service.confirm("email", "asha@mail.test", 41));
console.log("from config:", service.confirm(config.channel, "98765 43210", 41));
registry.register("push", () => new PushChannel()); // the whole change for a new channel: one class and this line
console.log(service.confirm("push", "asha-phone", 41));
try {
service.confirm("fax", "none", 41);
} catch (e) {
console.log("error:", e.message);
}email to asha@mail.test: Order 41 shipped, arriving Friday
from config: sms to 98765 43210: Order 41 shipped, ar
push to asha-phone: Order 41 shipped, arriving Friday
error: unknown channel: faxThe numbers: the third line is the new channel, and ConfirmationService has 0 lines of change. It never mentions a concrete class, and it is handed the registry, so a test can hand it a registry whose "email" entry builds a fake. This is the simple factory from the table: a registry of constructors, not one of the book's patterns, and in most Python and JavaScript code it is all you need. Each language brings its own form of "a constructor as a value":
| Language | A constructor as a value | What to know |
|---|---|---|
| Python | The class itself: registry.register("sms", SmsChannel) |
Classes are callable, so no wrapper is needed. Registries are often filled by a decorator or by __init_subclass__, which runs when a subclass is defined. A registry filled at import time depends on the module having been imported |
| Java | Supplier<Channel>, written SmsChannel::new |
java.util.ServiceLoader is the platform's own registry: it finds implementations listed in a provider file or a module declaration. A raw string key can be replaced by an enum, so a typo fails to compile |
| C++ | std::function<std::unique_ptr<Channel>()>, or a plain function pointer |
Return unique_ptr<Base>: the caller owns the object, and the base class needs a virtual destructor or deleting through the base pointer is undefined behaviour |
| JavaScript | An arrow function: () => new SmsChannel() |
A class cannot be called without new, so register a function that calls it. A Map keeps insertion order and takes any key |
When the constructors take different arguments, give every maker the same signature and let each adapt, as the rate limiter does: its FACTORIES entries all take (rule, clock), and each lambda picks what its limiter needs.
With a registry, a new channel is one new class and one new line in the table. The service that sends the message is not edited at all.
Factory Method: let the subclass choose#
Sometimes the code that needs the product is itself in a base class, and it cannot name the class because it does not know it. That is Factory Method: the base class has a fixed procedure and one overridable step that creates the product. Here a ConfirmationJob runs the same steps for every channel, and EmailJob and SmsJob decide which channel that is:
# factory_method.py: ConfirmationJob.run() is fixed; subclasses override create_channel()
from abc import ABC, abstractmethod
from typing import List
class Channel(ABC):
@abstractmethod
def send(self, to: str, text: str) -> str: ...
class EmailChannel(Channel):
def send(self, to: str, text: str) -> str:
return f"email to {to}: {text}"
class SmsChannel(Channel):
def send(self, to: str, text: str) -> str:
return f"sms to {to}: {text[:20]}" # an SMS is cut at 20 characters here
class ConfirmationJob(ABC): # the creator: run() never names a concrete channel
def run(self, to: str, order_id: int) -> str:
text = f"Order {order_id} shipped, arriving Friday"
return self.create_channel().send(to, text)
@abstractmethod
def create_channel(self) -> Channel: # the factory method
...
class EmailJob(ConfirmationJob):
def create_channel(self) -> Channel:
return EmailChannel()
class SmsJob(ConfirmationJob):
def create_channel(self) -> Channel:
return SmsChannel()
class RecordingChannel(Channel): # a test double that writes into a list it was given
def __init__(self, sent: List[str]) -> None:
self.sent = sent
def send(self, to: str, text: str) -> str:
self.sent.append(text)
return "recorded"
class TestJob(ConfirmationJob): # a test overrides the one method to avoid a real channel
def __init__(self) -> None:
self.sent: List[str] = []
def create_channel(self) -> Channel:
return RecordingChannel(self.sent)
print(EmailJob().run("asha@mail.test", 41))
print(SmsJob().run("98765 43210", 41))
job = TestJob()
result = job.run("anyone", 41)
print(f"test job: {result}, {len(job.sent)} message kept: {job.sent[0]}")// FactoryMethod.java: ConfirmationJob.run() is fixed; subclasses override createChannel()
import java.util.ArrayList;
import java.util.List;
class FactoryMethod {
interface Channel { String send(String to, String text); }
static final class EmailChannel implements Channel {
public String send(String to, String text) { return "email to " + to + ": " + text; }
}
static final class SmsChannel implements Channel {
public String send(String to, String text) {
return "sms to " + to + ": " + text.substring(0, Math.min(20, text.length())); // an SMS is cut at 20 characters here
}
}
static abstract class ConfirmationJob { // the creator: run() never names a concrete channel
String run(String to, int orderId) {
String text = "Order " + orderId + " shipped, arriving Friday";
return createChannel().send(to, text);
}
protected abstract Channel createChannel(); // the factory method
}
static final class EmailJob extends ConfirmationJob {
protected Channel createChannel() { return new EmailChannel(); }
}
static final class SmsJob extends ConfirmationJob {
protected Channel createChannel() { return new SmsChannel(); }
}
static final class RecordingChannel implements Channel { // a test double that writes into a list it was given
private final List<String> sent;
RecordingChannel(List<String> sent) { this.sent = sent; }
public String send(String to, String text) {
sent.add(text);
return "recorded";
}
}
static final class TestJob extends ConfirmationJob { // a test overrides the one method to avoid a real channel
final List<String> sent = new ArrayList<>();
protected Channel createChannel() { return new RecordingChannel(sent); }
}
public static void main(String[] args) {
System.out.println(new EmailJob().run("asha@mail.test", 41));
System.out.println(new SmsJob().run("98765 43210", 41));
TestJob job = new TestJob();
String result = job.run("anyone", 41);
System.out.println("test job: " + result + ", " + job.sent.size() + " message kept: " + job.sent.get(0));
}
}// factory_method.cpp: ConfirmationJob::run() is fixed; subclasses override create_channel()
#include <iostream>
#include <memory>
#include <string>
#include <vector>
struct Channel {
virtual ~Channel() = default;
virtual std::string send(const std::string& to, const std::string& text) const = 0;
};
struct EmailChannel : Channel {
std::string send(const std::string& to, const std::string& text) const override { return "email to " + to + ": " + text; }
};
struct SmsChannel : Channel {
std::string send(const std::string& to, const std::string& text) const override {
return "sms to " + to + ": " + text.substr(0, 20); // an SMS is cut at 20 characters here
}
};
class ConfirmationJob { // the creator: run() never names a concrete channel
public:
virtual ~ConfirmationJob() = default;
std::string run(const std::string& to, int order_id) {
std::string text = "Order " + std::to_string(order_id) + " shipped, arriving Friday";
return create_channel()->send(to, text);
}
protected:
virtual std::unique_ptr<Channel> create_channel() = 0; // the factory method
};
struct EmailJob : ConfirmationJob {
protected:
std::unique_ptr<Channel> create_channel() override { return std::make_unique<EmailChannel>(); }
};
struct SmsJob : ConfirmationJob {
protected:
std::unique_ptr<Channel> create_channel() override { return std::make_unique<SmsChannel>(); }
};
class RecordingChannel : public Channel { // a test double that writes into a list it was given
std::vector<std::string>& sent_;
public:
explicit RecordingChannel(std::vector<std::string>& sent) : sent_(sent) {}
std::string send(const std::string&, const std::string& text) const override {
sent_.push_back(text);
return "recorded";
}
};
class TestJob : public ConfirmationJob { // a test overrides the one method to avoid a real channel
public:
std::vector<std::string> sent;
protected:
std::unique_ptr<Channel> create_channel() override { return std::make_unique<RecordingChannel>(sent); }
};
int main() {
EmailJob email_job;
SmsJob sms_job;
std::cout << email_job.run("asha@mail.test", 41) << "\n";
std::cout << sms_job.run("98765 43210", 41) << "\n";
TestJob job;
std::string result = job.run("anyone", 41);
std::cout << "test job: " << result << ", " << job.sent.size() << " message kept: " << job.sent[0] << "\n";
}// factoryMethod.js: ConfirmationJob.run() is fixed; subclasses override createChannel()
class EmailChannel {
send(to, text) { return `email to ${to}: ${text}`; }
}
class SmsChannel {
send(to, text) { return `sms to ${to}: ${text.slice(0, 20)}`; } // an SMS is cut at 20 characters here
}
class ConfirmationJob { // the creator: run() never names a concrete channel
run(to, orderId) {
const text = `Order ${orderId} shipped, arriving Friday`;
return this.createChannel().send(to, text);
}
createChannel() { // the factory method: JavaScript has no abstract keyword, so the base throws
throw new Error("subclass must override createChannel");
}
}
class EmailJob extends ConfirmationJob {
createChannel() { return new EmailChannel(); }
}
class SmsJob extends ConfirmationJob {
createChannel() { return new SmsChannel(); }
}
class RecordingChannel { // a test double that writes into a list it was given
constructor(sent) { this.sent = sent; }
send(to, text) {
this.sent.push(text);
return "recorded";
}
}
class TestJob extends ConfirmationJob { // a test overrides the one method to avoid a real channel
sent = [];
createChannel() { return new RecordingChannel(this.sent); }
}
console.log(new EmailJob().run("asha@mail.test", 41));
console.log(new SmsJob().run("98765 43210", 41));
const job = new TestJob();
const result = job.run("anyone", 41);
console.log(`test job: ${result}, ${job.sent.length} message kept: ${job.sent[0]}`);email to asha@mail.test: Order 41 shipped, arriving Friday
sms to 98765 43210: Order 41 shipped, ar
test job: recorded, 1 message kept: Order 41 shipped, arriving Fridayrun() is written once. It builds the text and sends it, and the only line that differs between the three jobs is the one override. The test job shows the real use: a test replaces one method and the whole procedure runs against a recording channel. Three details are worth knowing:
- Do not call a factory method from a constructor. In C++, a virtual call from a base-class constructor runs the base version, because the derived part does not exist yet (here that would be a pure virtual call). In Java, the override runs before the subclass's own fields are initialised. JavaScript runs the override before the subclass's class fields exist, and Python runs it on an object whose subclass
__init__has not finished. - Factory Method needs inheritance, and a registry does not. If the only reason to subclass is to override one creation step, compare it with passing a maker into the class, which is the registry again, as a constructor argument. Inheritance is a good fit when the base class is a real framework procedure with several steps and the product is one of them.
- A factory method can take a parameter. The book also describes a parameterised factory method,
create_channel(kind), which a subclass overrides to add or change kinds. It is still Factory Method because it is an overridable step of a class. A free function with aswitchon a string is a simple factory. - A factory method can have a default. The base class may return a sensible default product and let subclasses override it. In the abstract form above, forgetting to override is a compile error (Java, C++) or an error at the first call (Python raises on instantiation because of
ABC; JavaScript whenrunis first called).
Abstract Factory: a family that goes together#
The second case is a checkout that serves more than one country. Each country has its own payment method, tax rule and invoice numbering, and they only make sense as a set. The client should get the set from one place:
# checkout_kit.py: one kit per country builds a matching payment, tax rule and invoice
from abc import ABC, abstractmethod
class Payment(ABC):
@abstractmethod
def pay(self, amount: int) -> str: ...
class TaxRule(ABC):
@abstractmethod
def on(self, subtotal: int) -> int: ...
class Invoice(ABC):
@abstractmethod
def number(self, order_no: int) -> str: ...
class UpiPayment(Payment):
def pay(self, amount: int) -> str:
return f"paid {amount} by UPI"
class CardPayment(Payment):
def pay(self, amount: int) -> str:
return f"paid {amount} by card"
class PercentTax(TaxRule):
def __init__(self, percent: int) -> None:
self.percent = percent
def on(self, subtotal: int) -> int:
return subtotal * self.percent // 100 # whole units, rounded down
class PrefixedInvoice(Invoice):
def __init__(self, prefix: str) -> None:
self.prefix = prefix
def number(self, order_no: int) -> str:
return f"{self.prefix}-{order_no:04d}"
class CheckoutKit(ABC): # the abstract factory: one create method per product
@abstractmethod
def create_payment(self) -> Payment: ...
@abstractmethod
def create_tax(self) -> TaxRule: ...
@abstractmethod
def create_invoice(self) -> Invoice: ...
class IndiaKit(CheckoutKit):
def create_payment(self) -> Payment:
return UpiPayment()
def create_tax(self) -> TaxRule:
return PercentTax(18)
def create_invoice(self) -> Invoice:
return PrefixedInvoice("IN")
class GermanyKit(CheckoutKit):
def create_payment(self) -> Payment:
return CardPayment()
def create_tax(self) -> TaxRule:
return PercentTax(19)
def create_invoice(self) -> Invoice:
return PrefixedInvoice("DE")
def checkout(kit: CheckoutKit, subtotal: int, order_no: int) -> str: # the client: sees only interfaces
tax = kit.create_tax().on(subtotal)
total = subtotal + tax
payment = kit.create_payment().pay(total)
invoice = kit.create_invoice().number(order_no)
return f"tax {tax}, total {total}, {payment}, invoice {invoice}"
KITS = {"IN": IndiaKit(), "DE": GermanyKit()} # the country code from config picks the whole family
for code in ("IN", "DE"):
print(f"{code}: {checkout(KITS[code], 1000, 41)}")// CheckoutKitDemo.java: one kit per country builds a matching payment, tax rule and invoice
import java.util.Map;
class CheckoutKitDemo {
interface Payment { String pay(int amount); }
interface TaxRule { int on(int subtotal); }
interface Invoice { String number(int orderNo); }
record UpiPayment() implements Payment {
public String pay(int amount) { return "paid " + amount + " by UPI"; }
}
record CardPayment() implements Payment {
public String pay(int amount) { return "paid " + amount + " by card"; }
}
record PercentTax(int percent) implements TaxRule {
public int on(int subtotal) { return subtotal * percent / 100; } // whole units, rounded down
}
record PrefixedInvoice(String prefix) implements Invoice {
public String number(int orderNo) { return prefix + "-" + String.format("%04d", orderNo); }
}
interface CheckoutKit { // the abstract factory: one create method per product
Payment createPayment();
TaxRule createTax();
Invoice createInvoice();
}
static final class IndiaKit implements CheckoutKit {
public Payment createPayment() { return new UpiPayment(); }
public TaxRule createTax() { return new PercentTax(18); }
public Invoice createInvoice() { return new PrefixedInvoice("IN"); }
}
static final class GermanyKit implements CheckoutKit {
public Payment createPayment() { return new CardPayment(); }
public TaxRule createTax() { return new PercentTax(19); }
public Invoice createInvoice() { return new PrefixedInvoice("DE"); }
}
static String checkout(CheckoutKit kit, int subtotal, int orderNo) { // the client: sees only interfaces
int tax = kit.createTax().on(subtotal);
int total = subtotal + tax;
String payment = kit.createPayment().pay(total);
String invoice = kit.createInvoice().number(orderNo);
return "tax " + tax + ", total " + total + ", " + payment + ", invoice " + invoice;
}
public static void main(String[] args) {
Map<String, CheckoutKit> kits = Map.of("IN", new IndiaKit(), "DE", new GermanyKit()); // the country code from config picks the whole family
for (String code : new String[] {"IN", "DE"}) {
System.out.println(code + ": " + checkout(kits.get(code), 1000, 41));
}
}
}// checkout_kit.cpp: one kit per country builds a matching payment, tax rule and invoice
#include <cstdio>
#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <utility>
struct Payment {
virtual ~Payment() = default;
virtual std::string pay(int amount) const = 0;
};
struct TaxRule {
virtual ~TaxRule() = default;
virtual int on(int subtotal) const = 0;
};
struct Invoice {
virtual ~Invoice() = default;
virtual std::string number(int order_no) const = 0;
};
struct UpiPayment : Payment {
std::string pay(int amount) const override { return "paid " + std::to_string(amount) + " by UPI"; }
};
struct CardPayment : Payment {
std::string pay(int amount) const override { return "paid " + std::to_string(amount) + " by card"; }
};
struct PercentTax : TaxRule {
int percent;
explicit PercentTax(int p) : percent(p) {}
int on(int subtotal) const override { return subtotal * percent / 100; } // whole units, rounded down
};
struct PrefixedInvoice : Invoice {
std::string prefix;
explicit PrefixedInvoice(std::string p) : prefix(std::move(p)) {}
std::string number(int order_no) const override {
char digits[16];
std::snprintf(digits, sizeof digits, "%04d", order_no);
return prefix + "-" + digits;
}
};
struct CheckoutKit { // the abstract factory: one create method per product
virtual ~CheckoutKit() = default;
virtual std::unique_ptr<Payment> create_payment() const = 0;
virtual std::unique_ptr<TaxRule> create_tax() const = 0;
virtual std::unique_ptr<Invoice> create_invoice() const = 0;
};
struct IndiaKit : CheckoutKit {
std::unique_ptr<Payment> create_payment() const override { return std::make_unique<UpiPayment>(); }
std::unique_ptr<TaxRule> create_tax() const override { return std::make_unique<PercentTax>(18); }
std::unique_ptr<Invoice> create_invoice() const override { return std::make_unique<PrefixedInvoice>("IN"); }
};
struct GermanyKit : CheckoutKit {
std::unique_ptr<Payment> create_payment() const override { return std::make_unique<CardPayment>(); }
std::unique_ptr<TaxRule> create_tax() const override { return std::make_unique<PercentTax>(19); }
std::unique_ptr<Invoice> create_invoice() const override { return std::make_unique<PrefixedInvoice>("DE"); }
};
std::string checkout(const CheckoutKit& kit, int subtotal, int order_no) { // the client: sees only interfaces
int tax = kit.create_tax()->on(subtotal);
int total = subtotal + tax;
std::string payment = kit.create_payment()->pay(total);
std::string invoice = kit.create_invoice()->number(order_no);
return "tax " + std::to_string(tax) + ", total " + std::to_string(total) + ", " + payment + ", invoice " + invoice;
}
int main() {
std::map<std::string, std::unique_ptr<CheckoutKit>> kits; // the country code from config picks the whole family
kits["IN"] = std::make_unique<IndiaKit>();
kits["DE"] = std::make_unique<GermanyKit>();
for (const char* code : {"IN", "DE"}) {
std::cout << code << ": " << checkout(*kits.at(code), 1000, 41) << "\n";
}
}// checkoutKit.js: one kit per country builds a matching payment, tax rule and invoice
class UpiPayment {
pay(amount) { return `paid ${amount} by UPI`; }
}
class CardPayment {
pay(amount) { return `paid ${amount} by card`; }
}
class PercentTax {
constructor(percent) { this.percent = percent; }
on(subtotal) { return Math.floor((subtotal * this.percent) / 100); } // whole units, rounded down
}
class PrefixedInvoice {
constructor(prefix) { this.prefix = prefix; }
number(orderNo) { return `${this.prefix}-${String(orderNo).padStart(4, "0")}`; }
}
class IndiaKit { // each kit is an abstract factory: one create method per product
createPayment() { return new UpiPayment(); }
createTax() { return new PercentTax(18); }
createInvoice() { return new PrefixedInvoice("IN"); }
}
class GermanyKit {
createPayment() { return new CardPayment(); }
createTax() { return new PercentTax(19); }
createInvoice() { return new PrefixedInvoice("DE"); }
}
function checkout(kit, subtotal, orderNo) { // the client: sees only the three create methods
const tax = kit.createTax().on(subtotal);
const total = subtotal + tax;
const payment = kit.createPayment().pay(total);
const invoice = kit.createInvoice().number(orderNo);
return `tax ${tax}, total ${total}, ${payment}, invoice ${invoice}`;
}
const kits = { IN: new IndiaKit(), DE: new GermanyKit() }; // the country code from config picks the whole family
for (const code of ["IN", "DE"]) {
console.log(`${code}: ${checkout(kits[code], 1000, 41)}`);
}IN: tax 180, total 1180, paid 1180 by UPI, invoice IN-0041
DE: tax 190, total 1190, paid 1190 by card, invoice DE-0041Check one row by hand: 1,000 at 18 percent is 180 of tax, so 1,180; at 19 percent it is 190, so 1,190. The client, checkout, is the same code for both and never names a country. What the pattern buys is consistency: you cannot get the Indian tax rule with the German invoice, because the client never picks products one at a time, it picks a kit once. The price is on the other axis. A new country is one new class. A new kind of product, say a receipt SMS, adds a method to CheckoutKit and a line in every kit, so Abstract Factory suits a set of kinds that is stable and a set of variants that grows.
In an Abstract Factory a new country is one new kit class. The checkout never changes, and its payment, tax and invoice always come from the same country.
How the same idea shows up across the four languages:
| Language | What to know |
|---|---|
| Python | A kit can be a plain dict of three callables, or a module with three functions. The ABC classes are worth it when you want a missing method to fail at start-up |
| Java | An interface with one method per product. record classes make the products short. A DI container (such as Spring) is a general-purpose factory, and choosing which set of beans is active per environment or country is its way of picking a kit |
| C++ | Return unique_ptr from each create method. When the family is known at compile time, a template parameter (checkout<IndiaKit>) gives the same consistency with no virtual calls |
| JavaScript | A kit is an object with three functions, so { createTax: () => ..., ... } works with no classes at all |
Reach for a registry when
- You build one kind of thing from a name or a setting
- The names come from config, or plugins register themselves
- You want a new variant to be one class and one line
- You are in Python or JavaScript, where a function or class is already a factory
Reach for the book's patterns when
- Factory Method: a base class runs a fixed procedure and one step is "make the product", which subclasses vary
- Abstract Factory: several products must come from the same variant, and the variants grow while the kinds of product stay put
- Nothing but a framework base class or a family of parts forces the structure
A kit with one product
A teammate proposes an EmailKit abstract factory with a single method, create_channel(), and one concrete kit per channel. Is that Abstract Factory? What would you build instead?
One strong answer
No: a family needs at least two products that must match. An interface with one create method is a plain factory, and a registry does the same job with less code. Abstract Factory earns its weight when swapping the variant has to swap several related objects together, as the country kit does. If one product is all you vary, use a registry or a function that returns it.
Factory Method, Abstract Factory and their look-alikes#
| Idea | What it is | How to tell it apart |
|---|---|---|
| Simple factory / registry | A function or table from name to object | No subclass overrides and no family. Often all you need, and the form the case studies use |
Static factory method (List.of, Integer.valueOf) |
A named constructor on the class | Not overridable. It is a naming and caching choice, not the book's pattern |
| Builder | Builds one complex object step by step | A factory returns a finished object from one call. A builder assembles one object over many calls |
| Prototype | Creates a new object by copying an existing one | A factory makes one from scratch. A prototype copies a template. An Abstract Factory is sometimes built from prototypes |
| Singleton | Exactly one instance | A factory may return the same object each time, but that is a policy it can change |
| Strategy | Swappable behaviour behind an interface | A factory often creates the strategy. Choosing and creating are separate jobs |
| Dependency injection | Handing a class what it needs | Inject a factory (or a registry) when the class has to create objects at run time, one per request, instead of receiving one at start-up |
| Service locator | A global registry that classes ask for their dependencies | Looks like a registry, but classes reach out to a global, so the dependency is hidden. Hand the registry in instead |
When not to use them#
- One concrete class, no variants.
new Email()is fine. A factory for a class with one implementation is ceremony: add it the day a second variant appears. - A
FooFactoryfor everyFoo. Factory classes with one method that calls one constructor are a sign of the habit, not the need. - Abstract Factory for a single product, or for products that do not have to match.
- Subclassing only to override one creation step when passing a function or a registry would do. Inheritance is the heaviest way to vary one line.
- A registry nobody extends. If the set of channels never grows and nothing is configured, the two-line
ifmay be the clearest code. The ladder was not wrong at two channels. It was costly because it was copied and it hid the class from tests.
A factory class for every class is ceremony. Use a plain new while nothing varies, and add a registry the day a second variant appears.
Where it shows up in the case studies#
| Case study | What | How |
|---|---|---|
| Parking lot | Factory function: build_lot() |
Builds the floors, the spots and the lot from a layout, so the lot's own class stays about running a lot. It is a simple factory: one function, one fixed product, no subclasses and no family |
| Rate limiter | Registry of factories: FACTORIES |
Maps "token_bucket", "fixed_window" or "sliding_log" from a rule's config to a function that builds that limiter from (rule, clock). The registry builds a limiter from a string, with no if/elif ladder. It is the simple factory of this lesson, filled from config |
Neither is the book's Factory Method or Abstract Factory. A fair interview answer says exactly that: "a factory function here, and a registry for config-driven choice, because I only vary one product."
In the interview#
Say it with the distinction: "I would use a registry of constructors, keyed by the name from config, so adding a channel is one class and one line and the service never names a concrete class. I would only reach for Factory Method if a base class owns the procedure and subclasses vary one step, and Abstract Factory if several products must come from the same variant, like payment, tax and invoice per country."
Then expect the follow-ups:
- "What is the difference between Factory Method and Abstract Factory?" Factory Method is one overridable method in a base class that creates one product. Abstract Factory is an object with several create methods that returns a family of products that match. They meet in the middle: each create method on a concrete kit is itself a factory method, which is how the book says Abstract Factory is usually implemented.
- "Is a factory function a design pattern?" It is an idiom, often called a simple factory. It is not one of the 23 in the book, and it is usually what you want.
- "How do you add a new product type to an Abstract Factory?" You add a method to the interface and to every concrete factory. That is its known cost. A new variant is cheap, a new kind of product is not.
- "How do the registry entries get there?" Explicitly at start-up in
main(best: visible and testable), or by decorators,__init_subclass__,ServiceLoaderor a plugin scan. The automatic ones depend on import or class-path order. - "What if the constructors take different arguments?" Give every maker one signature, such as
(rule, clock), and let each lambda adapt. - "How does it help testing?" The service is handed a registry or a kit, so a test hands it one that builds fakes. With a ladder inside the service there is nothing to hand in.
Common mistakes#
- Calling any function that builds an object "the Factory Method pattern". Name what it is: a simple factory, a static factory method, a Factory Method or an Abstract Factory.
- Calling a static
create()method Factory Method. Nothing is overridden, so it is not the book's pattern. - Abstract Factory with one product, or with products that never need to match.
- A big
if/elifladder inside the "factory", copied into three places. A table in one place is the fix. - A factory that returns the concrete type, so callers still depend on it.
- Calling a factory method from a constructor, and getting a half-built subclass or a pure virtual call.
- A registry keyed by raw strings that is checked only on first use, so a config typo becomes a production error. Check every configured name at start-up.
- Mixing families: building tax from one kit and the invoice from another, which is the exact bug Abstract Factory exists to prevent.
Factory Method and Abstract Factory
Q1/4 A service picks its notification channel with an if/elif ladder, and every new channel edits the service. What is the smallest fix that keeps the service from naming concrete classes?
Answer & explanation
Answer: B
A table from name to constructor, handed in, moves the choice out of the service, so a new channel is one class and one line. Abstract Factory needs a family of matching products, and a Singleton and a subclass per channel do not remove the choice.
Q2/4 What makes a method a Factory Method in the sense of the Design Patterns book?
Answer & explanation
Answer: C
The book's Factory Method is an overridable creation step that a base class calls and a subclass fills in. A static method such as List.of is a named constructor, and a switch on a string is a simple factory.
Q3/4 A checkout needs payment, tax and invoice rules that all belong to the customer's country. Which design stops an Indian tax rule from being paired with a German invoice?
Answer & explanation
Answer: B
A kit creates a matching family, and the client picks the kit once. Three independent functions can each be given a different country by mistake.
Q4/4 In which situation does Abstract Factory cost the most to change?
Answer & explanation
Answer: C
A new country is one new class, but a new product kind adds a method to the interface and to every concrete factory. That is why Abstract Factory fits a stable set of product kinds and a growing set of variants.
this lesson in shorts · 3
play them all →One idea from this lesson in about a minute each, made for your phone.
New lessons by email
New lessons, side quests and case files as they ship, at most once a week. No spam, unsubscribe anytime.
Discussion · …
Public: everyone reading this lesson sees comments. Be kind and keep it on the material.


