Creational Patterns
Structural Patterns
Behavioral Patterns
Pattern Pitfalls
Code Snippets
100
  • Purpose: Defines an interface for creating objects but lets subclasses alter the type of objects that will be created.
  • Common Uses:
    • Delegating object creation to subclasses.
    • Avoiding direct instantiation of specific classes.
  • Example: A GUI framework that allows different operating systems to create buttons and windows.




What is a Singleton?

100
  • Purpose: Converts the interface of a class into another interface that a client expects.
  • Common Uses:
    • Integrating incompatible interfaces in legacy systems.
    • Allowing classes with incompatible interfaces to work together.
  • Example:
    A power adapter that converts a two-prong plug to a three-prong socket.

What is an Adapter Pattern

100
  • Purpose: Passes a request along a chain of handlers until one handles it.
  • Common Uses:
    • Avoiding coupling between the sender and receiver of a request.
    • Processing requests in a hierarchical or conditional manner.
  • Example:
    A support ticket system where requests are escalated from a help desk to a manager and then to a director if unresolved.

What is a Chain of Responsibility Pattern?

100

Pitfalls:

  1. High Memory Overhead:
    • Storing the state of complex objects can consume a lot of memory, especially if frequent snapshots are required.
  2. Violation of Encapsulation:
    • Exposing too much internal state to this pattern can break encapsulation.
  3. Complex State Management:
    • Keeping track of multiple of these patterns can lead to bloated code if not managed properly.
  4. Undo Chain Complexity:
    • Implementing a reliable undo chain with multiple levels can be error-prone.

Mitigation:

  • Store only necessary state to reduce memory usage.
  • Use serialization or lightweight data structures for snapshots.
  • Clearly define what state should be externalized in the patterns data.

What are the pitfalls of the Memento Pattern?

100
class Singleton {
    private static instance: Singleton;

    private constructor() {}

    public static getInstance(): Singleton {
        if (!Singleton.instance) {
            Singleton.instance = new Singleton();
        }
        return Singleton.instance;
    }
}

// Usage
const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();
console.log(s1 === s2); // true

What is a Singleton Pattern?

200
  • Purpose: Defines an interface for creating objects but lets subclasses alter the type of objects that will be created.
  • Common Uses:
    • Delegating object creation to subclasses.
    • Avoiding direct instantiation of specific classes.
  • Example: A GUI framework that allows different operating systems to create buttons and windows.

What is a Factory Method?

200
  • Purpose: Decouples an abstraction from its implementation so that the two can vary independently.
  • Common Uses:
    • Avoiding a proliferation of subclasses by separating abstraction and implementation.
    • Supporting multiple dimensions of variation (e.g., platforms and functionalities).
  • Example:
    A device controller class (e.g., for TVs and remotes) that operates different devices using the same interface.

What is a Bridge Pattern?

200
  • Purpose: Encapsulates a request as an object, allowing for parameterization and queuing of requests.
  • Common Uses:
    • Undo/redo functionality.
    • Logging or queuing operations.
  • Example:
    A remote control where each button sends a specific command to a device.

What is a Command Pattern?

200

Pitfalls:

  1. Memory Leaks:
    • The watcher may not be removed properly, leading to dangling references and memory leaks.
  2. Performance Overhead:
    • Frequent notifications to a large number of observers can degrade performance.
  3. Tight Coupling:
    • Dependence on concrete implementations of this pattern can make the system harder to maintain.
  4. Unpredictable State Changes:
    • Observers may trigger cascading notifications, causing unpredictable behaviors.

Mitigation:

  • Use weak references to avoid memory leaks.
  • Optimize the notification mechanism (e.g., batch updates).
  • Follow the Law of Demeter to avoid tight coupling.
  • Clearly document notification triggers to prevent unintended state changes.

What are the pitfalls of the Observer Pattern?

200
// Abstract Product
interface Animal {
    speak(): string;
}

// Concrete Products
class Dog implements Animal {
    speak(): string {
        return "Woof!";
    }
}

class Cat implements Animal {
    speak(): string {
        return "Meow!";
    }
}
 
class AnimalFactory {
    static getAnimal(type: string): Animal {
        if (type === "dog") {
            return new Dog();
        } else if (type === "cat") {
            return new Cat();
        }
        throw new Error("Animal type not supported.");
    }
}

// Usage
const dog = AnimalFactory.getAnimal("dog");
console.log(dog.speak()); // "Woof!"
const cat = AnimalFactory.getAnimal("cat");
console.log(cat.speak()); // "Meow!"

What is the Factory Method Pattern?

300
  • Purpose: Separates the construction of a complex object from its representation, allowing the same construction process to create different representations.
  • Common Uses:
    • Building objects with multiple configuration steps.
    • Creating objects with optional or complex configurations.
  • Example: Building a house with different parts (e.g., walls, roof, windows).

What is a Builder Pattern

300
  • Purpose: Composes objects into tree structures to represent part-whole hierarchies, allowing clients to treat individual objects and compositions uniformly.
  • Common Uses:
    • Representing tree-like structures such as directories, organizational charts, or UI components.
  • Example:
    A file system where files and directories are treated the same way.

What is a Composite Pattern?

300
  • Purpose: Defines a representation for a language's grammar and provides an interpreter to process expressions in the language.
  • Common Uses:
    • Parsing expressions or commands in a domain-specific language.
    • Evaluating mathematical expressions.
  • Example:
    A calculator application that interprets and computes algebraic expressions.

What is an Interpreter?

300

Pitfalls:

  1. Pattern Explosion:
    • Complex systems may require a large number of states, making the design unwieldy.
  2. Hard to Debug:
    • Debugging can be challenging if state transitions are poorly documented or overly dynamic.
  3. Overhead for Simple Scenarios:
    • Using this pattern for simple workflows may add unnecessary complexity.
  4. Code Duplication:
    • Similar behavior across states may lead to redundant code.

Mitigation:

  • Consolidate similar states where possible.
  • Document state transitions clearly using a state diagram.
  • Use the pattern only for systems with well-defined state transitions.
  • Extract common behavior into helper classes or parent classes to reduce duplication.

What are the Pitfalls of the State Pattern 

300
// Subject
class Subject {
    private observers: Observer[] = [];

    addObserver(observer: Observer): void {
        this.observers.push(observer);
    }

    removeObserver(observer: Observer): void {
        this.observers = this.observers.filter(o => o !== observer);
    }

    notifyObservers(): void {
        this.observers.forEach(observer => observer.update());
    }
}

interface Observer {
    update(): void;
}

class ConcreteObserver implements Observer {
    constructor(private name: string) {}

    update(): void {
        console.log(`${this.name} has been notified!`);
    }
}

// Usage
const subject = new Subject();
const observer1 = new ConcreteObserver("Observer 1");
const observer2 = new ConcreteObserver("Observer 2");

subject.addObserver(observer1);
subject.addObserver(observer2);


subject.notifyObservers(); 
// Output:
// Observer 1 has been notified!
// Observer 2 has been notified!

What is the Observer Pattern?

400
  • Purpose: Creates new objects by copying an existing object (a prototype) rather than creating from scratch.
  • Common Uses:
    • Reducing the cost of creating objects (especially when initialization is expensive).
    • Cloning objects with similar properties but slight variations.
  • Example: A drawing application where existing shapes are cloned and modified.

What is a Prototype Pattern

400
  • Purpose: Dynamically adds new responsibilities to an object without modifying its structure.
  • Common Uses:
    • Extending the behavior of objects at runtime.
    • Avoiding a large hierarchy of subclasses for each variation.
  • Example:
    Adding scrolling functionality to a text editor widget without altering the base widget.

What is a Decorator Pattern?

400
  • Purpose: Provides a way to access elements of an aggregate object sequentially without exposing its underlying representation.
  • Common Uses:
    • Traversing collections like lists, arrays, or trees.
    • Implementing standardized iteration for custom data structures.
  • Example:
    A playlist system that iterates through songs in a music player.

What is an Iterator Pattern?

400

Pitfalls:

  1. Increased Number of Classes:
    • Each strategy requires its own class, which can clutter the codebase.
  2. Switching Strategies Dynamically:
    • Managing transitions between strategies at runtime can become complex.
  3. Tight Coupling:
    • If the client depends on specific strategy implementations, flexibility is reduced.
  4. Code Duplication Across Strategies:
    • Strategies that share similar logic can lead to duplication.

Mitigation:

  • Group related strategies into a single module to organize them better.
  • Provide clear APIs for switching strategies dynamically.
  • Use interfaces to decouple the client from specific strategy implementations.
  • Extract shared logic into helper methods or abstract base classes.

What are the pitfalls of the Strategy Pattern?

400
interface PaymentStrategy {
    pay(amount: number): void;
}

class CreditCardPayment implements PaymentStrategy {
    pay(amount: number): void {
        console.log(`Paid $${amount} using Credit Card.`);
    }
}

class PayPalPayment implements PaymentStrategy {
    pay(amount: number): void {
        console.log(`Paid $${amount} using PayPal.`);
    }
}

// Context
class PaymentContext {
    private strategy: PaymentStrategy;

    constructor(strategy: PaymentStrategy) {
        this.strategy = strategy;
    }

    setStrategy(strategy: PaymentStrategy): void {
        this.strategy = strategy;
    }

    executePayment(amount: number): void {
        this.strategy.pay(amount);
    }
}

// Usage
const context = new PaymentContext(new CreditCardPayment());
context.executePayment(100); // "Paid $100 using Credit Card."
context.setStrategy(new PayPalPayment());
context.executePayment(200); // "Paid $200 using PayPal."

What is the Strategy Pattern?

500
  • Purpose: Provides an interface for creating families of related or dependent objects without specifying their concrete classes.
  • Common Uses:
    • Creating groups of objects that are designed to work together (e.g., UI components for a specific theme).
  • Example: A factory that can create different kinds of widgets (buttons, text fields, etc.) for Windows or macOS.

What is an Abstract Factory?

500
  • Purpose: Provides a simplified interface to a larger body of code, such as a subsystem.
  • Common Uses:
    • Simplifying complex APIs for easier client interaction.
    • Hiding the complexity of subsystems from clients.
  • Example:
    A hotel booking system that provides a single interface for room booking, restaurant reservations, and transport arrangements.

What is a Facade?

500
  • Purpose: Defines an object that centralizes communication between other objects to reduce coupling.
  • Common Uses:
    • Simplifying communication in complex systems.
    • Coordinating interactions between UI components.
  • Example:
    A chat room application where the chat room (mediator) handles communication between users.

What is a Mediator Pattern?

500

Pitfalls:

  1. Inflexibility:
    • The predefined structure in the base class may not suit all variations.
  2. Difficult to Extend:
    • Adding new steps or changing the order of steps may require significant refactoring.
  3. Violation of Liskov Substitution Principle:
    • Subclasses may inadvertently break the base class logic if not carefully implemented.
  4. Overuse of Inheritance:
    • Heavy reliance on inheritance can lead to fragile hierarchies.

Mitigation:

  • Use composition over inheritance where appropriate to make the algorithm more flexible.
  • Clearly document the roles of steps in the algorithm to guide subclass implementations.
  • Validate subclasses to ensure they do not violate base class assumptions.
  • Limit the number of required steps to reduce rigidity.

What are the pitfalls of the Template Method Pattern?

500
// Abstract Class
abstract class DataProcessor {
    process(): void {
        this.loadData();
        this.processData();
        this.saveData();
    }

    protected abstract loadData(): void;
    protected abstract processData(): void;
    protected abstract saveData(): void;
}

// Concrete Class
class CSVProcessor extends DataProcessor {
    protected loadData(): void {
        console.log("Loading CSV data...");
    }

    protected processData(): void {
        console.log("Processing CSV data...");
    }

    protected saveData(): void {
        console.log("Saving CSV data...");
    }
}

// Usage
const processor = new CSVProcessor();
processor.process();
// Output:
// Loading CSV data...
// Processing CSV data...
// Saving CSV data...

What is the Template Method Pattern?

M
e
n
u