Skip to content

Latest commit

 

History

History
447 lines (344 loc) · 12.4 KB

FactoryMethodPattern.md

File metadata and controls

447 lines (344 loc) · 12.4 KB

Factory Method Design Pattern

This document explains the Factory Method pattern - a creational design pattern that provides an interface for creating objects while allowing subclasses to decide which class to instantiate.


What is the Factory Method Pattern?

The Factory Method defines an interface for creating objects, but lets subclasses decide which class to instantiate. It delegates the instantiation logic to child classes.

Real-world analogy: Think of a logistics company with different departments:

  • Land Logistics Department → Always sends trucks
  • Sea Logistics Department → Always sends ships
  • Air Logistics Department → Always sends planes

Each department knows which transport to use. The client just says "deliver the cargo" without caring about the specific vehicle.


The Problem It Solves

Without Factory Method (Tight Coupling):

// Client must know about all concrete classes
if(type === "land") {
  transport = new Truck();
} else if(type === "sea") {
  transport = new Ship();
}
// Adding new transport = Must modify this code!

With Factory Method (Decoupling):

// Client only knows about the abstract interface
const logistic = getLogistic(type);  // Gets the right factory
logistic.deliverCargo();             // Don't care which transport!
// Adding new transport = Just add a new Logistic subclass!

How It Works - Architecture

Structure Overview

┌───────────────────────────────────────┐
│         Client Code                    │
│  (testLogistic.js)                     │
│                                        │
│  logistic.deliverCargo();              │
└───────────────┬───────────────────────┘
                │ uses
                ↓
        ┌──────────────┐
        │   Logistic   │ (Abstract Class)
        ├──────────────┤
        │ + deliverCargo()              │ ← Template method
        │ + createTransport() [abstract]│ ← Factory method
        └──────────────┘
                ↑
        ┌───────┴────────┬────────────┐
        │                │            │
┌───────────────┐ ┌──────────────┐ ┌──────────────┐
│ LandLogistic  │ │ SeaLogistic  │ │ AirLogistic  │
├───────────────┤ ├──────────────┤ ├──────────────┤
│createTransport│ │createTransport│ │createTransport│
│  return Truck │ │  return Ship  │ │  return Plane │
└───────┬───────┘ └──────┬───────┘ └──────┬───────┘
        │                │                │
        ↓                ↓                ↓
    ┌───────┐        ┌───────┐       ┌───────┐
    │ Truck │        │ Ship  │       │ Plane │
    └───────┘        └───────┘       └───────┘

Code Implementation

1. Abstract Product (Transport)

Base interface that all transport types implement.

// transport/transport.js
class Transport {
  deliver() {
    throw new Error('deliver() must be implemented');
  }
}

2. Concrete Products (Truck, Ship)

Specific transport implementations.

// transport/truck.js
class Truck extends Transport {
  deliver() {
    console.log('Entrega feita por caminhão');
  }
}

// transport/ship.js
class Ship extends Transport {
  deliver() {
    console.log('Entrega feita por navio');
  }
}

3. Abstract Creator (Logistic)

Defines the factory method and uses it in the template method.

// logistic/logistic.js
class Logistic {
  // Factory Method - subclasses override this
  createTransport() {
    throw new Error('This method should be implemented!');
  }

  // Template Method - uses the factory method
  deliverCargo() {
    const transport = this.createTransport();  // ← Factory method call
    transport.deliver();
  }
}

Key points:

  • createTransport() is the factory method (abstract)
  • deliverCargo() is the template method (concrete)
  • Template method calls the factory method

4. Concrete Creators (LandLogistic, SeaLogistic)

Each subclass implements the factory method to create its specific transport.

// logistic/landLogistic.js
class LandLogistic extends Logistic {
  createTransport() {
    return new Truck();  // ← Creates specific product
  }
}

// logistic/seaLogistic.js
class SeaLogistic extends Logistic {
  createTransport() {
    return new Ship();  // ← Creates specific product
  }
}

5. Client Code

Client works with the abstract Logistic class, not concrete classes.

// testLogistic.js
const LandLogistic = require('./logistic/landLogistic');
const SeaLogistic = require('./logistic/seaLogistic');

let logistic;
const logisticType = "land";

if(logisticType === "land") {
  logistic = new LandLogistic();
} else if (logisticType === "sea") {
  logistic = new SeaLogistic();
}

logistic.deliverCargo();  // Client doesn't know which transport!

Key Concepts

Decoupling

What is decoupling? Reducing dependencies between components so they can change independently.

How Factory Method achieves decoupling:

Before (Tight Coupling):

// Client depends on ALL concrete transport classes
const truck = new Truck();
const ship = new Ship();
const plane = new Plane();
// Client must import and know about every transport type

After (Loose Coupling):

// Client depends only on abstract Logistic class
const logistic = new LandLogistic();  // or SeaLogistic, or AirLogistic
logistic.deliverCargo();
// Client doesn't import or know about Truck, Ship, Plane

Benefits:

  • Client code doesn't change when adding new transport types
  • Transport classes can change without affecting client
  • Easy to swap implementations

Template Method + Factory Method

The pattern uses two methods working together:

Factory Method: createTransport()

  • Abstract method that subclasses override
  • Returns the specific product (transport)
  • The "factory" that creates objects

Template Method: deliverCargo()

  • Concrete method in base class
  • Defines the algorithm/workflow
  • Uses the factory method internally
deliverCargo() {
  const transport = this.createTransport();  // Step 1: Create (factory)
  transport.deliver();                        // Step 2: Use
}

When to Use Factory Method

✅ Good Use Cases

  1. You don't know the exact types of objects in advance

    • User input determines which object to create
    • Configuration-based object creation
  2. You want to provide extension points

    • Allow subclasses to customize object creation
    • Framework/library providing hooks
  3. You want to reuse existing objects

    • Factory can implement object pooling
    • Factory can return cached instances
  4. You need to decouple creation from usage

    • Client shouldn't know about concrete classes
    • Reduce dependencies
  5. You follow Open/Closed Principle

    • Add new product types without modifying existing code
    • Extend through subclassing

❌ When NOT to Use

  1. Simple object creation

    • If new MyClass() is sufficient, don't overcomplicate
  2. Only one product type

    • No need for abstraction if there's only one implementation
  3. No variation in creation logic

    • If all products are created the same way

Advantages

Benefit Explanation
Decoupling Client doesn't depend on concrete product classes
Open/Closed Add new products without modifying existing code
Single Responsibility Creation logic centralized in creator classes
Flexibility Easy to introduce new product types
Testability Easy to mock factories for testing

Adding New Transport Type

Scenario: Add air transport support.

Without Factory Method (Inefficient Approach)

// ❌ Must modify existing file
if(transportType === "caminhao") {
  transport = new Truck();
} else if(transportType === "navio"){
  transport = new Ship();
} else if(transportType === "aviao"){  // Modified existing code!
  transport = new Plane();
}

With Factory Method (Efficient Approach)

// ✅ Just add new files, no modification to existing code

// 1. Create new product
// transport/plane.js
class Plane extends Transport {
  deliver() {
    console.log('Entrega feita por avião');
  }
}

// 2. Create new creator
// logistic/airLogistic.js
class AirLogistic extends Logistic {
  createTransport() {
    return new Plane();
  }
}

// 3. Use it (client code unchanged!)
const logistic = new AirLogistic();
logistic.deliverCargo();

No modification to:

  • ❌ Logistic.js
  • ❌ Transport.js
  • ❌ LandLogistic.js
  • ❌ SeaLogistic.js
  • ❌ Existing transport classes

Only additions:

  • ✅ Plane.js (new)
  • ✅ AirLogistic.js (new)

Comparison: Before vs After

Before Factory Method

Problems:
❌ Tight coupling to concrete classes
❌ If/else chain grows with each new type
❌ Client must know about all products
❌ Violates Open/Closed Principle
❌ Hard to test and maintain

After Factory Method

Benefits:
✅ Loose coupling via abstraction
✅ No if/else chain in main logic
✅ Client only knows about abstract types
✅ Follows Open/Closed Principle
✅ Easy to test and maintain

Real-World Examples

Where Factory Method is used:

  1. Database Connections

    • MySQLConnection, PostgreSQLConnection, MongoDBConnection
    • ConnectionFactory creates the right connection
  2. UI Components

    • WindowsButton, MacButton, LinuxButton
    • UIFactory creates OS-specific buttons
  3. Document Processors

    • PDFDocument, WordDocument, ExcelDocument
    • DocumentFactory creates the right processor
  4. Payment Gateways

    • PayPalPayment, StripePayment, CreditCardPayment
    • PaymentFactory creates the right gateway

Decoupling in Action

Tight Coupling (Bad):

// Client knows everything
import Truck from './truck';
import Ship from './ship';
import Plane from './plane';

const transport = new Truck();  // Hardcoded dependency
transport.deliver();

Loose Coupling (Good):

// Client knows only the interface
import Logistic from './logistic';

const logistic = createLogistic(type);  // Factory handles creation
logistic.deliverCargo();                // Client doesn't care about details

What decoupling gives you:

  • Change implementation without changing client
  • Test with mock objects easily
  • Swap implementations at runtime
  • Independent development of components

Key Takeaways

Concept Summary
Pattern Name Factory Method
Type Creational Design Pattern
Purpose Define interface for creating objects, let subclasses decide which class to instantiate
Key Benefit Decoupling - client doesn't depend on concrete classes
When to Use When you need flexibility in object creation and want to follow Open/Closed Principle
Core Idea Delegate object creation to subclasses through a factory method

Summary

The Factory Method pattern is your solution to tight coupling and rigid object creation.

Remember:

  • 🏭 Factory Method = Subclasses decide which class to instantiate
  • 🔌 Decoupling = Client depends on abstractions, not concrete classes
  • 📖 Open/Closed = Open for extension, closed for modification
  • ➕ Adding features = Create new classes, don't modify existing ones
  • 🧪 Testing = Easy to mock and test in isolation

The Factory Method pattern makes your code:

  • Flexible and extensible
  • Easy to maintain and test
  • Loosely coupled and modular
  • Following SOLID principles

Compare with InefficientApproach-Problems.md to see what problems this pattern solves!