MSDN Documentation

.NET Concepts: Design Patterns

Understanding Design Patterns in .NET

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

Creational Patterns

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

Factory Method

Defines an interface for creating an object, but lets subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

// Example (Simplified)
public abstract class Creator
{
    public abstract Product FactoryMethod();

    public string SomeOperation()
    {
        Product product = this.FactoryMethod();
        return "Creator: The same creator's code has just worked with " + product.Operation();
    }
}

public class ConcreteCreatorA : Creator
{
    public override Product FactoryMethod()
    {
        return new ConcreteProductA();
    }
}

public abstract class Product
{
    public abstract string Operation();
}

public class ConcreteProductA : Product
{
    public override string Operation()
    {
        return "{Result of ConcreteProductA}";
    }
}

Singleton

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

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

    private Singleton() { }

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

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

Structural Patterns

These patterns are concerned with class and object composition. They use inheritance to compose interfaces for common functionality.

Adapter

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

// Target Interface
public interface ITarget
{
    string Request();
}

// Adaptee (incompatible interface)
public class Adaptee
{
    public string SpecificRequest()
    {
        return "Called SpecificRequest()";
    }
}

// Adapter
public class Adapter : ITarget
{
    private readonly Adaptee _adaptee;

    public Adapter(Adaptee adaptee)
    {
        _adaptee = adaptee;
    }

    public string Request()
    {
        return "Adapter: " + _adaptee.SpecificRequest();
    }
}

Decorator

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

// Component Interface
public interface IDecoratorComponent
{
    string Operation();
}

// Concrete Component
public class ConcreteDecoratorComponent : IDecoratorComponent
{
    public string Operation()
    {
        return "ConcreteDecoratorComponent";
    }
}

// Base Decorator Class
public abstract Decorator : IDecoratorComponent
{
    protected IDecoratorComponent _wrappedComponent;

    public Decorator(IDecoratorComponent component)
    {
        _wrappedComponent = component;
    }

    public virtual string Operation()
    {
        return _wrappedComponent.Operation();
    }
}

// Concrete Decorator
public class ConcreteDecoratorA : Decorator
{
    public ConcreteDecoratorA(IDecoratorComponent component) : base(component) { }

    public override string Operation()
    {
        return "ConcreteDecoratorA(" + base.Operation() + ")";
    }
}

Behavioral Patterns

These patterns are concerned with algorithms and the assignment of responsibilities between objects.

Observer

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

// Subject
public interface ISubject
{
    void Attach(IObserver observer);
    void Detach(IObserver observer);
    void Notify();
}

// Concrete Subject
public class ConcreteSubject : ISubject
{
    public int State { get; set; }
    private List<IObserver> _observers = new List<IObserver>();

    public void Attach(IObserver observer)
    {
        _observers.Add(observer);
    }

    public void Detach(IObserver observer)
    {
        _observers.Remove(observer);
    }

    public void Notify()
    {
        foreach (var observer in _observers)
        {
            observer.Update(this);
        }
    }
}

// Observer Interface
public interface IObserver
{
    void Update(ISubject subject);
}

// Concrete Observer
public class ConcreteObserverA : IObserver
{
    public void Update(ISubject subject)
    {
        if (subject is ConcreteSubject concreteSubject)
        {
            Console.WriteLine($"Observer A received update. New state: {concreteSubject.State}");
        }
    }
}

Strategy

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

// Strategy Interface
public interface IStrategy
{
    string Execute(string data);
}

// Concrete Strategies
public class ConcreteStrategyA : IStrategy
{
    public string Execute(string data)
    {
        return "ConcreteStrategyA processed: " + data.ToLower();
    }
}

public class ConcreteStrategyB : IStrategy
{
    public string Execute(string data)
    {
        return "ConcreteStrategyB processed: " + data.ToUpper();
    }
}

// Context
public class Context
{
    private IStrategy _strategy;

    public Context() { }

    public void SetStrategy(IStrategy strategy)
    {
        _strategy = strategy;
    }

    public string ExecuteStrategy(string data)
    {
        if (_strategy == null)
        {
            throw new InvalidOperationException("Strategy not set.");
        }
        return _strategy.Execute(data);
    }
}

Exploring and implementing design patterns can significantly improve the quality, maintainability, and scalability of your .NET applications.

Explore More Patterns Back to Concepts