MSDN Documentation

Comprehensive Guides and Tutorials for Developers

Software Design Patterns

Explore fundamental software design patterns that are widely used in modern application development. Understanding these patterns can lead to more robust, maintainable, and flexible code.

Creational Patterns

These patterns are concerned with object creation mechanisms, trying to create objects in a manner suitable to the situation.

  • Singleton Pattern

    Ensures a class has only one instance and provides a global point of access to it.

  • Factory Method Pattern

    Defines an interface for creating an object, but lets subclasses decide which class to instantiate.

  • Abstract Factory Pattern

    Provides an interface for creating families of related or dependent objects without specifying their concrete classes.

  • Builder Pattern

    Separates the construction of a complex object from its representation so that the same construction process can create different representations.

  • Prototype Pattern

    Specifies the kinds of objects that a class creates, and this is done by using the prototypes that are created and copied.

Structural Patterns

These patterns deal with class and object composition. They explain how to assemble objects and classes into larger structures.

  • Adapter Pattern

    Converts the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.

  • Bridge Pattern

    Decouples an abstraction from its implementation so that the two can vary independently.

  • Composite Pattern

    Composes objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.

  • Decorator Pattern

    Attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

  • Facade Pattern

    Provides a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.

  • Flyweight Pattern

    Uses sharing to support large numbers of fine-grained objects efficiently.

  • Proxy Pattern

    Provides a surrogate or placeholder for another object to control access to it.

Behavioral Patterns

These patterns are concerned with algorithms and the assignment of responsibilities between objects. They characterize complex control flow that cannot be modeled by simple static structures.

  • Chain of Responsibility Pattern

    Avoids coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chains the receiving objects and passes the request along the chain until an object handles it.

  • Command Pattern

    Encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

  • Interpreter Pattern

    Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.

  • Iterator Pattern

    Provides a way to access the elements of an aggregate object (like a collection) sequentially without exposing its underlying representation.

  • Mediator Pattern

    Defines an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.

  • Memento Pattern

    Captures and externalizes an object's internal state so that the object can be restored to this state later, without violating encapsulation.

  • Observer Pattern

    Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

  • State Pattern

    Allows an object to alter its behavior when its internal state changes. The object will appear to change its class.

  • Strategy Pattern

    Defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

  • Template Method Pattern

    Defines the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.

  • Visitor Pattern

    Represents an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

Detailed Explanations and Examples

Click on a pattern name above or browse through the sections below for in-depth explanations, problem statements, solutions, and code examples in various programming languages.

Singleton Pattern

Problem

Ensure that a class only has one instance and provide a global point of access to it. This is often useful for managing shared resources like database connections or configuration settings.

Solution

The Singleton pattern involves a private constructor, a static member to hold the single instance, and a public static method to access that instance. Lazy initialization or eager initialization can be used.

Example Snippet (Conceptual)

// Conceptual Singleton
class Singleton {
    private static instance: Singleton | null = null;

    private constructor() {
        // Private constructor to prevent direct instantiation
    }

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

    public someOperation(): void {
        console.log("Executing some operation.");
    }
}

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

console.log(s1 === s2); // true
s1.someOperation();
                    

Factory Method Pattern

Problem

Define an interface for creating an object, but allow subclasses to decide which class to instantiate. This pattern decouples the client code from the concrete classes it needs to create.

Solution

A creator class declares the factory method, which is supposed to return an object of a Product type. The concrete creator classes implement the factory method to return an instance of a concrete product.

Example Snippet (Conceptual)

// Conceptual Factory Method
interface Product {
    operation(): string;
}

class ConcreteProductA implements Product {
    operation(): string {
        return "Result of ConcreteProductA";
    }
}

class ConcreteProductB implements Product {
    operation(): string {
        return "Result of ConcreteProductB";
    }
}

abstract class Creator {
    public abstract factoryMethod(): Product;

    public someOperation(): string {
        const product = this.factoryMethod();
        return `Creator: The same creator's code has just worked with ${product.operation()}`;
    }
}

class ConcreteCreatorA extends Creator {
    public factoryMethod(): Product {
        return new ConcreteProductA();
    }
}

class ConcreteCreatorB extends Creator {
    public factoryMethod(): Product {
        return new ConcreteProductB();
    }
}

// Usage
function clientCode(creator: Creator) {
    console.log("Client: I'm not aware of the creator's class, but it still works.");
    console.log(creator.someOperation());
}

clientCode(new ConcreteCreatorA());
clientCode(new ConcreteCreatorB());
                    

Adapter Pattern

Problem

Allow objects with incompatible interfaces to collaborate. This is common when working with legacy code or third-party libraries.

Solution

The Adapter pattern acts as a bridge between two incompatible interfaces. It wraps one of the objects and provides an interface that the client expects.

Example Snippet (Conceptual)

// Conceptual Adapter
class Target {
    request(): string {
        return "Target: The default target's behavior.";
    }
}

class Adaptee {
    specificRequest(): string {
        return "Adaptee: The adaptee's business logic.";
    }
}

class Adapter extends Target {
    private adaptee: Adaptee;

    constructor(adaptee: Adaptee) {
        super();
        this.adaptee = adaptee;
    }

    public request(): string {
        return `Adapter: (TRANSLATED) ${this.adaptee.specificRequest()}`;
    }
}

// Usage
function clientCode(target: Target) {
    console.log(target.request());
}

const adaptee = new Adaptee();
clientCode(new Adapter(adaptee));
                    

Observer Pattern

Problem

Define a one-to-many dependency between objects. When one object (the subject) changes state, all its dependents (observers) are notified and updated automatically.

Solution

The Subject maintains a list of Observers. When its state changes, it iterates over the list and calls an update method on each Observer.

Example Snippet (Conceptual)

// Conceptual Observer
interface Observer {
    update(subject: Subject): void;
}

interface Subject {
    attach(observer: Observer): void;
    detach(observer: Observer): void;
    notify(): void;
}

class ConcreteSubject implements Subject {
    public state: number = 0;
    private observers: Observer[] = [];

    public attach(observer: Observer): void {
        const isObserverAttached = this.observers.includes(observer);
        if (isObserverAttached) {
            return console.log('Subject: Observer attached twice.');
        }
        this.observers.push(observer);
        console.log('Subject: Attached an observer.');
    }

    public detach(observer: Observer): void {
        const observerIndex = this.observers.indexOf(observer);
        if (observerIndex === -1) {
            return console.log('Subject: Nonexistent observer.');
        }
        this.observers.splice(observerIndex, 1);
        console.log('Subject: Detached an observer.');
    }

    public notify(): void {
        console.log('Subject: Notifying observers...');
        for (const observer of this.observers) {
            observer.update(this);
        }
    }

    public businessLogic(): void {
        console.log('Subject: Performing business logic...');
        this.state = Math.floor(Math.random() * (10 + 1));
        console.log(`Subject: My state has changed to: ${this.state}`);
        this.notify();
    }
}

class ConcreteObserverA implements Observer {
    public update(subject: Subject): void {
        if ((subject as ConcreteSubject).state % 2 === 0) {
            console.log('ConcreteObserverA: Reacted to the event.');
        }
    }
}

class ConcreteObserverB implements Observer {
    public update(subject: Subject): void {
        console.log('ConcreteObserverB: Reacted to the event.');
    }
}

// Usage
const subject = new ConcreteSubject();
const observerA = new ConcreteObserverA();
subject.attach(observerA);

const observerB = new ConcreteObserverB();
subject.attach(observerB);

subject.businessLogic();
subject.businessLogic();

subject.detach(observerB);
subject.businessLogic();
                    

Strategy Pattern

Problem

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

Solution

The Strategy pattern involves a context class that holds a reference to a Strategy object and delegates the execution of the algorithm to the Strategy object. Concrete strategies implement the algorithm in different ways.

Example Snippet (Conceptual)

// Conceptual Strategy
interface Strategy {
    execute(data: string): string;
}

class ConcreteStrategyA implements Strategy {
    execute(data: string): string {
        return `ConcreteStrategyA: ${data.split('').reverse().join('')}`;
    }
}

class ConcreteStrategyB implements Strategy {
    execute(data: string): string {
        return `ConcreteStrategyB: ${data.toUpperCase()}`;
    }
}

class Context {
    private strategy: Strategy;

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

    public setStrategy(strategy: Strategy) {
        this.strategy = strategy;
    }

    public doSomeBusinessLogic(): string {
        const result = this.strategy.execute("some data");
        return `Context: The result of the strategy. \n${result}`;
    }
}

// Usage
const context = new Context(new ConcreteStrategyA());
console.log("Client: Strategy is set to normal sorting.");
console.log(context.doSomeBusinessLogic());

console.log("\nClient: Strategy is changed to upper-casing.");
context.setStrategy(new ConcreteStrategyB());
console.log(context.doSomeBusinessLogic());
                    

Visitor Pattern

Problem

Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

Solution

The Visitor pattern consists of two main parts: the Visitor interface (which declares a visit method for each concrete element type) and the concrete Visitor classes (which implement the operations). The element classes have an accept method that takes a visitor and calls the appropriate visit method on it.

Example Snippet (Conceptual)

// Conceptual Visitor
interface Visitor {
    visitConcreteElementA(concreteElementA: ConcreteElementA): void;
    visitConcreteElementB(concreteElementB: ConcreteElementB): void;
}

interface Element {
    accept(visitor: Visitor): void;
}

class ConcreteElementA implements Element {
    accept(visitor: Visitor): void {
        visitor.visitConcreteElementA(this);
    }

    public exclusiveA(): string {
        return "This is exclusive method for ConcreteElementA.";
    }
}

class ConcreteElementB implements Element {
    accept(visitor: Visitor): void {
        visitor.visitConcreteElementB(this);
    }

    public exclusiveB(): string {
        return "This is exclusive method for ConcreteElementB.";
    }
}

class ConcreteVisitor1 implements Visitor {
    visitConcreteElementA(concreteElementA: ConcreteElementA): void {
        console.log(`${concreteElementA.exclusiveA()} -- ConcreteVisitor1`);
    }

    visitConcreteElementB(concreteElementB: ConcreteElementB): void {
        console.log(`${concreteElementB.exclusiveB()} -- ConcreteVisitor1`);
    }
}

class ConcreteVisitor2 implements Visitor {
    visitConcreteElementA(concreteElementA: ConcreteElementA): void {
        console.log(`${concreteElementA.exclusiveA()} -- ConcreteVisitor2`);
    }

    visitConcreteElementB(concreteElementB: ConcreteElementB): void {
        console.log(`${concreteElementB.exclusiveB()} -- ConcreteVisitor2`);
    }
}

// Usage
const elements: Element[] = [
    new ConcreteElementA(),
    new ConcreteElementB(),
];

const visitor1 = new ConcreteVisitor1();
for (const element of elements) {
    element.accept(visitor1);
}

console.log("\n");

const visitor2 = new ConcreteVisitor2();
for (const element of elements) {
    element.accept(visitor2);
}