Factory and Abstract Factory

Topics Covered

The Problem with Direct Construction

A Notification System That Breaks

The Root Cause

Factory Method Pattern

Document Creator Example

Structure of the Pattern

When to Use Factory Method

Abstract Factory Pattern

UI Theme Example

Why Families Matter

Adding a New Theme

Factory vs Constructor

When Constructors Are Enough

When Factories Win

The Decision Rule

Choosing the Right Factory

Decision Framework

Common Mistakes

Practice Problems

Every time you write new EmailNotifier() in your code, you are making a decision: this specific line will always create an email notifier, no matter what. That decision is now hardcoded into your business logic. What happens when the product team says "we also need SMS notifications"? You hunt through the codebase, find every place that says new EmailNotifier(), and add conditional logic. What happens when they add push notifications next quarter? You do it all again.

This is the core problem with direct construction: it scatters creation decisions across your entire codebase, coupling your logic to specific concrete classes.

Direct construction coupling versus factory-based decoupling

A Notification System That Breaks

Here is a simple notification system where the service directly creates notifiers:

python
1class OrderService:
2    def place_order(self, order):
3        # Business logic
4        self.process_payment(order)
5        self.update_inventory(order)
6
7        # Notification — hardcoded to email
8        notifier = EmailNotifier()
9        notifier.send(order.customer, "Order confirmed!")

This works until the requirements change. Now some customers prefer SMS:

python
1class OrderService:
2    def place_order(self, order):
3        self.process_payment(order)
4        self.update_inventory(order)
5
6        # Now we need conditional logic everywhere
7        if order.customer.prefers_sms:
8            notifier = SMSNotifier()
9        else:
10            notifier = EmailNotifier()
11        notifier.send(order.customer, "Order confirmed!")

Every service that sends notifications, OrderService, ShippingService, ReturnService, now contains the same if/else block. When push notifications arrive, you modify every one of those blocks. When you add Slack notifications for enterprise customers, you modify them all again. The creation logic is duplicated, scattered, and tightly coupled to the business logic that should not care about how notifiers are built.

Common Pitfall

If adding a new type requires changes in more than one file, your creation logic is scattered. Factory patterns centralize that decision into a single location, so adding a new type means changing one class instead of twenty.

The Root Cause

The problem is not the new keyword itself: it is the placement. When business logic directly constructs specific classes, two concerns get tangled together: what to do (send a notification) and which specific object to use (an email notifier). These are separate decisions that change for different reasons. The order service changes when business rules change. The notifier type changes when communication channels change. Mixing them means every communication channel change forces you to modify business logic classes.

The fix is to separate creation from usage. Something else decides which notifier to build. The business logic just receives a Notifier and calls send(). That "something else" is a factory.