1.1 Factory
The Factory pattern answers a deceptively simple, endlessly recurring question: who decides which concrete class to new?
The moment you write new EmailNotifier(), the caller is welded to that concrete class. As soon as you need SMS, push, or Slack, the caller fills up with if/else branches — and that branching gets copy-pasted into every place that sends a notification. The Factory pattern takes the "which object do I create" decision away from the caller and concentrates it in one place.
The factory order counter below shows this: you only tell the factory which kind of notification you want, and it picks and builds the right concrete class — while the client call factory.create(channel).send(msg) never changes.
Simple Factory, Factory Method, and Abstract Factory
"Factory" is really a family of ideas. Let's untangle the names:
- Simple Factory: one method that uses
switch/ifon a parameter to return different subclasses. It is not an official GoF pattern, but it is the most common and approachable.
- Factory Method: define "create" as an abstract method and let subclasses decide which class to instantiate. It extends products through inheritance.
- Abstract Factory: create a whole family of related products — the focus of the next section.
This section centers on the most practical two: Simple Factory and Factory Method. A typical factory-method skeleton:
interface Notifier {
void send(String message);
}
class NotifierFactory {
Notifier create(String channel) {
switch (channel) {
case "email": return new EmailNotifier();
case "sms": return new SmsNotifier();
case "push": return new PushNotifier();
default: throw new IllegalArgumentException(channel);
}
}
}The caller only sees the Notifier interface; adding a channel means adding one implementation class and one line in the factory.
Why this is an Open/Closed win
When creation logic is scattered, adding a product type means hunting down every new and every if and editing each one. Miss one and you have a bug.
Centralize creation in a factory and a new type touches only that one place — the call sites stay frozen. That is exactly what the Open/Closed Principle wants (open for extension, closed for modification). The comparator below lets you watch the number of "code points that must change" rise and fall depending on whether you use a factory.
When not to use a factory
Patterns cost something. If:
- you have exactly one concrete implementation and won't have a second any time soon;
- creation is a single
newwith no parameter choice and no branching;
then a plain new is the clearest option. Introducing a factory too early just adds a layer of pointless indirection that forces every reader to take an extra hop. Wait for repeated creation pressure, then introduce the factory.
create() stuffed with dozens of unrelated products. When the branches pile up, split with a registry (a map from key to supplier) or with polymorphism.The exercise below has you refactor a "new everywhere" notification snippet into a version that depends on a factory and an interface.