MSDN Documentation

Advanced Topics > Design Patterns

Design Patterns

Design patterns are reusable solutions to commonly occurring problems within a given context in software design. They are not final designs that can be translated directly into code, but rather descriptions or templates for how to solve a problem that can be used in many different situations.

This section explores fundamental and advanced design patterns, providing explanations, examples, and best practices for their implementation in modern software development.

Categorization of Design Patterns

Design patterns are often categorized into three main types:

Key Design Patterns

1. Singleton Pattern

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

When to Use:
Example Use Case:

Managing a single database connection pool or a global configuration manager.

Code Snippet (Conceptual C#):

public sealed class Singleton
{
    private static readonly Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton Instance
    {
        get
        {
            return instance;
        }
    }

    public void DoSomething()
    {
        // ...
    }
}
            

2. Factory Method Pattern

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

When to Use:
Example Use Case:

A document editor that can create different types of documents (e.g., text, spreadsheet, presentation).

Code Snippet (Conceptual Java):

abstract class Document {
    public abstract void open();
}

class TextDocument extends Document {
    @Override
    public void open() {
        System.out.println("Opening text document...");
    }
}

abstract class Application {
    public abstract Document createDocument();

    public void newDocument() {
        Document doc = createDocument();
        doc.open();
    }
}

class TextEditor extends Application {
    @Override
    public Document createDocument() {
        return new TextDocument();
    }
}
            

3. 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.

When to Use:
Example Use Case:

Stock market tickers, weather updates, or UI elements reacting to data changes.

Code Snippet (Conceptual JavaScript):

class Subject {
    constructor() {
        this.observers = [];
    }

    addObserver(observer) {
        this.observers.push(observer);
    }

    notify(data) {
        this.observers.forEach(observer => observer.update(data));
    }
}

class ConcreteObserver {
    update(data) {
        console.log("Observer received:", data);
    }
}

const subject = new Subject();
const observer1 = new ConcreteObserver();
const observer2 = new ConcreteObserver();

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

subject.notify("New data available!");
            

4. 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.

When to Use:
Example Use Case:

Implementing different sorting algorithms for a list, or different payment methods in an e-commerce system.

5. Decorator Pattern

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

When to Use:
Example Use Case:

Adding logging, compression, or encryption to data streams.

6. Command Pattern

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

When to Use:
Example Use Case:

Implementing undo/redo functionality in an editor, or creating a remote control for various devices.

Explore further to understand the nuances of each pattern, their advantages, disadvantages, and when to best apply them in your software architecture.

Further Reading and Resources