Software Development Design Patterns are proven, reusable solutions to common software design problems. They provide a structured approach to writing code, improving maintainability, scalability, and flexibility. Instead of reinventing the wheel, developers can rely on these patterns to solve problems efficiently while following industry best practices.
The concept of design patterns was popularized in software engineering by the “Gang of Four” (GoF)—Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides—in their 1994 book, Design Patterns: Elements of Reusable Object-Oriented Software. The book categorized design patterns into Creational, Structural, and Behavioral patterns, which remain widely used today.
Design patterns are typically categorized into three main types:
Here’s a detailed explanation of each Design Patterns:
Creational design patterns deal with object instantiation by providing flexible, reusable, and scalable solutions. Instead of directly instantiating objects using the new
keyword, these patterns encapsulate the object creation logic, allowing greater flexibility and decoupling between the code and the created objects.
These patterns ensure:
✅ Encapsulation of instantiation logic (hiding complex creation processes).
✅ Improved code reusability and flexibility (switching between different implementations is easier).
✅ Better management of object lifecycles (reducing memory and performance issues).
Ensures that a class has only one instance throughout the program and provides a global access point to it.
When a single shared resource (e.g., logging service, database connection) is required.
To control access to a shared state (e.g., configuration settings).
public class Singleton
{
private static Singleton instance;
private static readonly object lockObject = new object();
private Singleton() {} // Private constructor
public static Singleton Instance
{
get
{
lock (lockObject)
{
if (instance == null)
{
instance = new Singleton();
}
}
return instance;
}
}
}
Defines an interface for creating objects, but allows subclasses to determine the actual implementation.
When the exact type of object to be created isn’t known until runtime.
When you need to decouple object creation from the main logic.
public abstract class Product
{
public abstract void Operation();
}
public class ConcreteProductA : Product
{
public override void Operation() => Console.WriteLine("Product A created.");
}
public class ConcreteProductB : Product
{
public override void Operation() => Console.WriteLine("Product B created.");
}
public abstract class Creator
{
public abstract Product FactoryMethod();
}
public class ConcreteCreatorA : Creator
{
public override Product FactoryMethod() => new ConcreteProductA();
}
public class ConcreteCreatorB : Creator
{
public override Product FactoryMethod() => new ConcreteProductB();
}
✅ Encapsulates object creation and promotes loose coupling.
Provides an interface for creating families of related objects without specifying their concrete classes.
When you need to create multiple related objects that must be used together.
When object creation depends on a specific theme/configuration (e.g., GUI toolkits with different themes).
public interface IButton
{
void Render();
}
public class WindowsButton : IButton
{
public void Render() => Console.WriteLine("Windows Button Rendered.");
}
public class MacButton : IButton
{
public void Render() => Console.WriteLine("Mac Button Rendered.");
}
public interface IGUIFactory
{
IButton CreateButton();
}
public class WindowsFactory : IGUIFactory
{
public IButton CreateButton() => new WindowsButton();
}
public class MacFactory : IGUIFactory
{
public IButton CreateButton() => new MacButton();
}
// Client Code
IGUIFactory factory = new WindowsFactory();
IButton button = factory.CreateButton();
button.Render(); // Outputs: "Windows Button Rendered."
✅ Decouples product creation from its implementation.
Separates the construction process of a complex object from its representation, allowing different representations using the same construction process.
When creating complex objects with multiple optional attributes.
When you need step-by-step construction and want to avoid a large constructor with multiple parameters.
public class Car
{
public string Engine { get; set; }
public int Wheels { get; set; }
public bool GPS { get; set; }
public void Show() => Console.WriteLine($"Car with {Engine}, {Wheels} wheels, GPS: {GPS}");
}
public class CarBuilder
{
private Car car = new Car();
public CarBuilder SetEngine(string engine)
{
car.Engine = engine;
return this;
}
public CarBuilder SetWheels(int wheels)
{
car.Wheels = wheels;
return this;
}
public CarBuilder SetGPS(bool gps)
{
car.GPS = gps;
return this;
}
public Car Build() => car;
}
// Client Code
Car car = new CarBuilder().SetEngine("V8").SetWheels(4).SetGPS(true).Build();
car.Show(); // Outputs: "Car with V8, 4 wheels, GPS: True"
✅ Fluent API, step-by-step object creation, and readable code.
Allows creating new objects by copying an existing object (cloning) instead of creating from scratch.
When object creation is costly (e.g., database operations, network calls).
When you need duplicate objects with minor modifications.
public abstract class Prototype
{
public abstract Prototype Clone();
}
public class ConcretePrototype : Prototype
{
public string Data { get; set; }
public override Prototype Clone() => (Prototype)this.MemberwiseClone();
}
// Client Code
ConcretePrototype obj1 = new ConcretePrototype { Data = "Original Data" };
ConcretePrototype obj2 = (ConcretePrototype)obj1.Clone();
obj2.Data = "Cloned Data";
Console.WriteLine(obj1.Data); // Outputs: "Original Data"
Console.WriteLine(obj2.Data); // Outputs: "Cloned Data"
✅ Efficient cloning instead of expensive instantiation.
Creational patterns improve object creation efficiency, scalability, and flexibility in software development. Each pattern serves a specific use case:
Singleton: Single instance access.
Factory Method: Dynamic object creation.
Abstract Factory: Creating related object families.
Builder: Step-by-step object creation.
Prototype: Cloning existing objects.
By choosing the right pattern for your scenario, you can enhance code reusability, maintainability, and flexibility. 🚀
Structural design patterns focus on how classes and objects are composed to form larger structures while ensuring flexibility and efficiency. These patterns simplify relationships between objects, making it easier to create complex systems while maintaining loose coupling and reusability.
✅ Improve Code Organization – Helps arrange classes and objects efficiently.
✅ Encapsulate Complexity – Provides an abstraction over complex systems.
✅ Enhance Maintainability – Promotes modular and loosely coupled code.
✅ Promote Reusability – Encourages composition over inheritance.
Allows two incompatible interfaces to work together by providing a wrapper (adapter) that translates requests from one interface to another.
When working with legacy code that has an incompatible interface.
When integrating third-party libraries that don’t match your existing code.
// Incompatible interface
public interface ITarget
{
void Request();
}
// Adaptee (Incompatible class)
public class Adaptee
{
public void SpecificRequest() => Console.WriteLine("Adaptee's Specific Request");
}
// Adapter (Makes Adaptee compatible with ITarget)
public class Adapter : ITarget
{
private readonly Adaptee _adaptee;
public Adapter(Adaptee adaptee) => _adaptee = adaptee;
public void Request() => _adaptee.SpecificRequest();
}
// Client Code
ITarget adapter = new Adapter(new Adaptee());
adapter.Request(); // Outputs: "Adaptee's Specific Request"
✅ Bridges compatibility issues without modifying existing code.
Allows adding new functionality to an object dynamically at runtime without modifying its structure.
When you need flexible feature additions without altering existing code.
When subclassing would lead to a combinatorial explosion of classes.
// Base component
public interface IComponent
{
void Operation();
}
// Concrete component
public class ConcreteComponent : IComponent
{
public void Operation() => Console.WriteLine("Base Operation");
}
// Decorator base class
public class Decorator : IComponent
{
protected IComponent _component;
public Decorator(IComponent component) => _component = component;
public virtual void Operation() => _component.Operation();
}
// Concrete Decorator (adds behavior)
public class ConcreteDecorator : Decorator
{
public ConcreteDecorator(IComponent component) : base(component) { }
public override void Operation()
{
base.Operation();
Console.WriteLine("Added Behavior");
}
}
// Client Code
IComponent component = new ConcreteDecorator(new ConcreteComponent());
component.Operation();
// Outputs:
// "Base Operation"
// "Added Behavior"
✅ Enhances flexibility by dynamically modifying objects at runtime.
Provides a unified, simplified interface to a set of complex subsystems, making it easier to use them.
When dealing with a complex system with many interdependent classes.
When you need a simpler API for client code.
public class SubsystemA
{
public void OperationA() => Console.WriteLine("Subsystem A operation");
}
public class SubsystemB
{
public void OperationB() => Console.WriteLine("Subsystem B operation");
}
// Facade (Simplified interface)
public class Facade
{
private readonly SubsystemA _subsystemA = new();
private readonly SubsystemB _subsystemB = new();
public void SimplifiedOperation()
{
_subsystemA.OperationA();
_subsystemB.OperationB();
}
}
// Client Code
Facade facade = new Facade();
facade.SimplifiedOperation();
// Outputs:
// "Subsystem A operation"
// "Subsystem B operation"
✅ Reduces system complexity and promotes better encapsulation.
Allows treating individual objects and compositions uniformly, making it easier to work with tree structures.
When working with hierarchical structures like trees.
When you need to treat single objects and collections uniformly.
public interface IComponent
{
void Display();
}
// Leaf node
public class Leaf : IComponent
{
private string _name;
public Leaf(string name) => _name = name;
public void Display() => Console.WriteLine(_name);
}
// Composite node
public class Composite : IComponent
{
private readonly List<IComponent> _children = new();
public void Add(IComponent component) => _children.Add(component);
public void Remove(IComponent component) => _children.Remove(component);
public void Display()
{
foreach (var component in _children)
component.Display();
}
}
// Client Code
Composite tree = new Composite();
tree.Add(new Leaf("Leaf 1"));
tree.Add(new Leaf("Leaf 2"));
Composite subtree = new Composite();
subtree.Add(new Leaf("Leaf 3"));
tree.Add(subtree);
tree.Display();
// Outputs:
// "Leaf 1"
// "Leaf 2"
// "Leaf 3"
✅ Simplifies hierarchical structures (e.g., UI elements, file systems).
Acts as a substitute or intermediary for another object, controlling access to it.
When you need lazy initialization (loading objects only when needed).
When implementing security, logging, or caching before accessing an object.
public interface ISubject
{
void Request();
}
// Real Subject
public class RealSubject : ISubject
{
public void Request() => Console.WriteLine("Real Subject Request");
}
// Proxy
public class Proxy : ISubject
{
private RealSubject _realSubject;
public void Request()
{
if (_realSubject == null)
_realSubject = new RealSubject();
Console.WriteLine("Proxy controlling access...");
_realSubject.Request();
}
}
// Client Code
ISubject proxy = new Proxy();
proxy.Request();
// Outputs:
// "Proxy controlling access..."
// "Real Subject Request"
✅ Improves performance (e.g., virtual proxies, caching proxies).
Structural design patterns help in organizing and optimizing object relationships in software systems. Each pattern serves a unique purpose:
Adapter – Bridges incompatible interfaces.
Decorator – Adds behavior dynamically.
Facade – Simplifies a complex subsystem.
Composite – Works with hierarchical structures.
Proxy – Controls access to an object.
By applying these patterns, you can make your software more modular, reusable, and scalable. 🚀
Behavioral design patterns focus on how objects interact and communicate with each other. These patterns improve flexibility, scalability, and maintainability by reducing dependencies between objects and ensuring efficient execution of behaviors.
✅ Promotes Loose Coupling – Reduces dependencies between objects.
✅ Encapsulates Behaviors – Defines clear interaction rules.
✅ Increases Code Flexibility – Enables runtime behavior changes.
✅ Enhances Code Maintainability – Avoids hardcoded logic in objects.
Defines a family of algorithms, encapsulates each one, and allows switching between them at runtime without altering client code.
When multiple algorithms exist for a task and should be interchangeable.
When if-else or switch-case statements are becoming complex.
// Strategy Interface
public interface IStrategy
{
void Execute();
}
// Concrete Strategies
public class ConcreteStrategyA : IStrategy
{
public void Execute() => Console.WriteLine("Executing Strategy A");
}
public class ConcreteStrategyB : IStrategy
{
public void Execute() => Console.WriteLine("Executing Strategy B");
}
// Context that uses the strategy
public class Context
{
private IStrategy _strategy;
public void SetStrategy(IStrategy strategy) => _strategy = strategy;
public void ExecuteStrategy() => _strategy.Execute();
}
// Client Code
Context context = new Context();
context.SetStrategy(new ConcreteStrategyA());
context.ExecuteStrategy(); // Outputs: "Executing Strategy A"
context.SetStrategy(new ConcreteStrategyB());
context.ExecuteStrategy(); // Outputs: "Executing Strategy B"
✅ Encapsulates algorithms, making them interchangeable.
Defines a dependency between objects so that when one changes, all dependents are notified automatically.
When implementing event-driven systems (e.g., UI event listeners, notifications).
When multiple objects must react to changes in another object.
// Observer Interface
public interface IObserver
{
void Update(string message);
}
// Subject (Publisher)
public class Subject
{
private List<IObserver> _observers = new();
public void Attach(IObserver observer) => _observers.Add(observer);
public void Detach(IObserver observer) => _observers.Remove(observer);
public void Notify(string message)
{
foreach (var observer in _observers)
observer.Update(message);
}
}
// Concrete Observer
public class ConcreteObserver : IObserver
{
private string _name;
public ConcreteObserver(string name) => _name = name;
public void Update(string message) => Console.WriteLine($"{_name} received: {message}");
}
// Client Code
Subject subject = new Subject();
IObserver observer1 = new ConcreteObserver("Observer 1");
IObserver observer2 = new ConcreteObserver("Observer 2");
subject.Attach(observer1);
subject.Attach(observer2);
subject.Notify("Event triggered!");
// Outputs:
// "Observer 1 received: Event triggered!"
// "Observer 2 received: Event triggered!"
✅ Promotes decoupled communication between objects.
Encapsulates a request as an object, allowing parameterization and queuing of requests.
When implementing undo/redo operations.
When executing commands dynamically at runtime.
// Command Interface
public interface ICommand
{
void Execute();
}
// Receiver
public class Receiver
{
public void Action() => Console.WriteLine("Receiver action executed.");
}
// Concrete Command
public class ConcreteCommand : ICommand
{
private Receiver _receiver;
public ConcreteCommand(Receiver receiver) => _receiver = receiver;
public void Execute() => _receiver.Action();
}
// Invoker
public class Invoker
{
private ICommand _command;
public void SetCommand(ICommand command) => _command = command;
public void ExecuteCommand() => _command.Execute();
}
// Client Code
Receiver receiver = new Receiver();
ICommand command = new ConcreteCommand(receiver);
Invoker invoker = new Invoker();
invoker.SetCommand(command);
invoker.ExecuteCommand(); // Outputs: "Receiver action executed."
✅ Encapsulates requests, enabling better command execution and undo operations.
Allows multiple handlers to process a request sequentially until one handles it.
When multiple objects might handle a request, but the exact handler is unknown.
When implementing logging, validation, authentication flows.
public abstract class Handler
{
protected Handler _nextHandler;
public void SetNext(Handler nextHandler) => _nextHandler = nextHandler;
public abstract void HandleRequest(int request);
}
// Concrete Handlers
public class ConcreteHandlerA : Handler
{
public override void HandleRequest(int request)
{
if (request < 10)
Console.WriteLine("Handled by ConcreteHandlerA");
else
_nextHandler?.HandleRequest(request);
}
}
public class ConcreteHandlerB : Handler
{
public override void HandleRequest(int request)
{
Console.WriteLine("Handled by ConcreteHandlerB");
}
}
// Client Code
Handler handlerA = new ConcreteHandlerA();
Handler handlerB = new ConcreteHandlerB();
handlerA.SetNext(handlerB);
handlerA.HandleRequest(5); // Outputs: "Handled by ConcreteHandlerA"
handlerA.HandleRequest(15); // Outputs: "Handled by ConcreteHandlerB"
✅ Promotes flexible request handling without hardcoded conditions.
Encapsulates communication between objects, reducing direct dependencies.
When objects communicate too much, causing tight coupling.
When implementing chat rooms, traffic control, UI interactions.
// Mediator Interface
public interface IMediator
{
void SendMessage(string message, Colleague colleague);
}
// Concrete Mediator
public class ConcreteMediator : IMediator
{
private List<Colleague> _colleagues = new();
public void Register(Colleague colleague) => _colleagues.Add(colleague);
public void SendMessage(string message, Colleague sender)
{
foreach (var colleague in _colleagues)
if (colleague != sender)
colleague.Receive(message);
}
}
// Colleague Base Class
public abstract class Colleague
{
protected IMediator _mediator;
public Colleague(IMediator mediator) => _mediator = mediator;
public abstract void Receive(string message);
}
// Concrete Colleague
public class ConcreteColleague : Colleague
{
public ConcreteColleague(IMediator mediator) : base(mediator) { }
public override void Receive(string message) => Console.WriteLine($"Colleague received: {message}");
}
// Client Code
IMediator mediator = new ConcreteMediator();
Colleague colleague1 = new ConcreteColleague(mediator);
Colleague colleague2 = new ConcreteColleague(mediator);
((ConcreteMediator)mediator).Register(colleague1);
((ConcreteMediator)mediator).Register(colleague2);
colleague1.Receive("Hello from Colleague1!");
// Outputs:
// "Colleague received: Hello from Colleague1!"
✅ Reduces direct dependencies between objects, improving maintainability.
The State Pattern allows an object to alter its behavior when its internal state changes, making it appear as if the object changed its class. Instead of using complex if-else or switch statements, the State Pattern encapsulates different behaviors into separate state classes and dynamically changes them at runtime.
✅ Encapsulates State-Specific Behavior – Avoids scattered if-else conditions.
✅ Promotes Open-Closed Principle – Easily add new states without modifying existing code.
✅ Improves Maintainability – Organizes code by grouping behavior into state classes.
When an object has multiple states, and its behavior changes based on its current state.
When you want to avoid large if-else or switch-case statements for handling state changes.
When you need to ensure that only valid state transitions occur (e.g., order processing, workflow management, traffic lights).
Draft (Initial State)
Moderation (Waiting for approval)
Published (Approved and visible)
Each state has different behaviors when transitioning to the next state.
Each state will implement an interface defining possible actions.
// State Interface
public interface IDocumentState
{
void Publish(Document document);
void Review(Document document);
}
Each class represents a specific state and defines behavior for transitioning to other states.
// Concrete State: Draft
public class DraftState : IDocumentState
{
public void Publish(Document document)
{
Console.WriteLine("Cannot publish directly from Draft. Send for review first.");
}
public void Review(Document document)
{
Console.WriteLine("Document is now under review.");
document.SetState(new ModerationState()); // Change state to Moderation
}
}
// Concrete State: Moderation (Waiting for approval)
public class ModerationState : IDocumentState
{
public void Publish(Document document)
{
Console.WriteLine("Document approved and published.");
document.SetState(new PublishedState()); // Change state to Published
}
public void Review(Document document)
{
Console.WriteLine("Document is already under review.");
}
}
// Concrete State: Published
public class PublishedState : IDocumentState
{
public void Publish(Document document)
{
Console.WriteLine("Document is already published.");
}
public void Review(Document document)
{
Console.WriteLine("Published documents cannot be reviewed again.");
}
}
This class maintains the current state and delegates actions to the current state.
public class Document
{
private IDocumentState _state;
public Document()
{
// Initial state is Draft
_state = new DraftState();
}
public void SetState(IDocumentState state)
{
_state = state;
}
public void Publish()
{
_state.Publish(this);
}
public void Review()
{
_state.Review(this);
}
}
The client interacts with the Document class without worrying about state-specific logic.
class Program
{
static void Main()
{
Document document = new Document();
document.Publish(); // Cannot publish directly from Draft. Send for review first.
document.Review(); // Document is now under review.
document.Publish(); // Document approved and published.
document.Review(); // Published documents cannot be reviewed again.
}
}
Output:
Cannot publish directly from Draft. Send for review first.
Document is now under review.
Document approved and published.
Published documents cannot be reviewed again.
✔ Encapsulates behaviors into separate state classes instead of using long if-else conditions.
✔ Allows an object to change behavior dynamically by switching states.
✔ Follows the Single Responsibility Principle (SRP) by keeping each state’s logic separate.
❌ If there are only two states with minimal behavior changes, a simple boolean flag (isActive
, isEnabled
) may be sufficient.
❌ If state transitions rarely change, using enums or simple conditions might be more efficient.
Traffic Light System (Red → Yellow → Green)
Order Processing System (New → Processing → Shipped → Delivered)
ATM Machine (Idle → Card Inserted → Processing → Transaction Complete)
Behavioral patterns focus on communication, control, and execution in software systems:
Strategy – Switch between algorithms dynamically.
Observer – Implement event-driven communication.
Command – Encapsulate and queue requests.
Chain of Responsibility – Pass requests along handlers.
Mediator – Centralize object communication.
State – helps manage state-dependent behavior dynamically while keeping the code clean, maintainable, and scalable. By using separate state classes, you avoid complex conditionals and ensure proper transitions between states
By applying these patterns, your software will become more modular, maintainable, and adaptable! 🚀
Design patterns are the language of software architecture. By understanding and applying the correct patterns:
You write more modular and flexible code.
Your solutions become easier to understand, maintain, and scale.
You communicate design intentions more effectively across teams.
Whether you’re building a microservice backend, a UI component library, or a scalable system, design patterns are a key part of your developer toolbox. So start practicing them in your daily coding work—and see your solutions evolve from functional to elegant.
🚀 Ready to take your code to the next level? Design patterns are the way.
Nearshore Software Services Partner
Service oriented, cost-effective, reliable and well-timed specialized in Software Development and located in Costa Rica