Free E-BOOK on Design Patterns In Use

Dr Milan Milanović
11 min readFeb 19, 2024

--

The concept of design patterns in software engineering was popularized in the early 1990s by the famous book “ Design Patterns: Elements of Reusable Object-Oriented Software” by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, collectively known as the “Gang of Four” (GoF). However, the roots of design patterns go back further, drawing inspiration from the field of architecture.

Architect Christopher Alexander introduced the idea of patterns in architecture in his 1977 book “ A Pattern Language,” where he described solutions to common design problems in urban planning and building architecture. Alexander’s work emphasized that each pattern solves a specific problem and is part of a more extensive design system. This approach resonated with software developers facing similar challenges in software construction.

Seeing the potential of Alexander’s concepts in software development, the GoF adapted and expanded these ideas to object-oriented programming. Their book introduced 23 design patterns categorized into Creational, Structural, and Behavioral patterns, providing a standardized approach to solving common software design issues.

In software development, design patterns serve a similar purpose- they provide templated solutions to recurring problems, ensuring that you don’t have to reinvent the wheel each time you encounter a familiar issue.

This article is accompanied by a live GitHub repo with all examples and the book in PDF format.

Design Pattern Types

Choosing the correct design pattern in software engineering is critical to practical problem-solving. This guide simplifies the process, helping you decide between patterns based on specific needs. It offers concise descriptions and valuable use cases for each pattern, making understanding and applying them in real-world scenarios easier.

To select a pattern, we must first go through the problem identification. If the problem is related to:

  • Object Creation? → Creational Patterns
  • Object Assembly? → Structural Patterns
  • Object Interactions? → Behavioral Patterns

Don’t fall into the Design Patterns trap

It is best to be warned that you will fall into the Design Patterns trap when you first learn design patterns. This means you will try to squeeze a pattern in every solution, and your codebase will become over-engineering and unusable very soon.

But we want to make our codebase as simple as possible, so Design patterns are only the silver bullet for some problems. We should not try to put them into every problem we have because they are solutions to problems, not tools that should be used everywhere. If you can implement a simple solution without using a design pattern, do it!

Seniority in Software Engineering (Credits: @flaviocopes)

During many years of using these patterns, I noticed that some are used often, some rarely, and some are not. Here, I will present only those patterns you need daily.

Every pattern has an implementation in C# language in the GitHub repo.

Creational Design Patterns

Creational patterns focus on instantiating an object or group of related objects.

Let’s go deep into the most critical creational patterns.

Singleton

Usage: Use when a single instance of a class is needed. Some examples are logging and database connections.

Real-world example: Only one CEO leads the company, making decisions and representing the entire organization, like a singleton providing global access and control.

The remark about usage: The Singleton pattern is regarded as an anti-pattern; hence, using them excessively is advised. Why? Using it, we make procedural code with global variables.

UML diagram of Singleton pattern:

An example in C#:

The Singleton pattern in C#

An important note about the Singleton pattern is that it poses a significant issue for multithreading, especially in a scenario where multiple threads may simultaneously attempt to create an instance of the Singleton class. This can lead to multiple instances if proper synchronization is not enforced, thus violating the core principle of the Singleton pattern that only one instance of the class should ever exist.

There are multiple solutions to this problem, such as eager initialization, double-checked locking, or lazy initialization.

The Singleton pattern with thread-safe lazy initialization by default

Factory method

Usage: Decouple object creation from usage. For example, you create different types of database connections based on configuration.

Real-world example: Think of a pizza joint with a “Pizza Factory” instead of chefs. Customers order “cheese” or “pepperoni,” not knowing how it’s made. Based on the order, this factory tells specialized “CheesePizza” or “PepperoniPizza” builders to get cookin’. Each builder adds signature toppings, keeping the creation logic separate but the ordering process smooth.

The remark about usage: It can lead to increased classes, potentially making the codebase more complex.

UML diagram of Factory Method pattern:

An example in C#:

An example of a Factory method in C#

And now the usage of the factory:

Builder

Usage: Constructing complex objects step by step. For example, if you need to create a complex domain object.

Real-world example: If we hire an architect to design our dream home, we don’t need to know every construction detail. We need to tell the architect our preferences (number of rooms, style, materials), and they create a blueprint with those specifications. The architect acts as the “builder pattern,” guiding us through the creation process with clear steps (foundation, walls, roof), ensuring correct order, and handling complex details. You make choices (fireplace or no fireplace?), and the builder incorporates them, constructing the house piece by piece until it’s complete.

The remark about usage: It could lead to increased complexity due to the introduction of multiple new classes.

UML diagram of Builder pattern:

An example in C# (creating a system to assemble customized computers):

Builder pattern in C#

The usage of the builder:

Usage of the Builder pattern

Other exciting design patterns from this group are:

  • Abstract Factory: Create families of related objects. For example, I build parsers for different file formats (e.g., JSON, XML, CSV). Check the implementation in C#.
  • Prototype: Creating duplicate objects and reusing cached objects to reduce database calls. Check the implementation in C#.

Structural Patterns

Structural patterns are primarily concerned with object composition or, in other words, how the entities can use each other.

Here are the most critical structural patterns we should know about.

Adapter

Usage: Make incompatible interfaces compatible. For example, it integrates a new logging library into an existing system that expects a different interface.

Real-world example: Allows you to use your devices in different countries by adapting to the local power outlet (adapter mediates communication between incompatible systems).

The remark about usage: Can lead to an increase in the number of adapters, making the system more complicated. Modifying the service class to align with the rest of the codebase could be simpler sometimes.

UML diagram of Adapter pattern:

An example in C#:

The Adapter pattern in C#

A concrete usage of this pattern would be:

Usage of the Adapter pattern

Composite

Usage: Represent part-whole hierarchies. For example, graphic objects in a drawing application can be grouped and treated uniformly.

Real-world example: In the library, books are organized on shelves, but each shelf can further hold categories (fiction, history). These categories might even contain subcategories (romance, mystery). Each shelf acts as a composite, keeping both individual books (leaf nodes) and other categories (composite nodes).

The remark about usage: Restricting operations for certain components or leaves in the hierarchy could be challenging.

UML diagram of Composite pattern:

An example in C#:

The Composite pattern in C#

The usage of the Composite would look like this:

Usage of the Composite pattern

Proxy

Usage: Control access to objects. For example, lazy loading of a high-resolution image in a web application.

Real-world example: Let’s assume you’re a CEO with a personal assistant who acts as a “proxy,” handling requests and shielding you from unnecessary distractions. The assistant assesses each request, prioritizing the necessary and specific, filtering out spam, and preparing relevant info. Only the filtered essentials reach the CEO, who focuses on big decisions, while the assistant handles the rest.

The remark about usage: Overusing proxies can add unnecessary complexity and impact performance.

UML diagram of Proxy pattern:

An example in C# (a Proxy pattern is used to control access to sensitive documents):

The Proxy pattern in C#

The usage of this pattern would look like this:

Usage of the Proxy pattern

Other interesting design patterns from this group are:

  • Decorator Pattern. It is used to add/remove behavior dynamically. For example, we are implementing compression or encryption on top of file streams. Check the implementation in C#.
  • Bridge pattern. It is used to decouple abstraction from implementation. For example, I am separating platform-specific code from core logic. Check the implementation in C#.
  • Facade pattern. It provides a simplified interface to a complex subsystem. Check the implementation in C#.

Behavioral patterns

They focus on distributing responsibility among the objects. They differ from structural patterns in that they define the patterns for message conveyance and communication between them in addition to the structure.

Here are the most critical behavioral patterns we should know about.

Strategy

Usage: Define a family of algorithms. For example, they allow users to choose different sorting or compression algorithms.

Real-world example: Let’s plan a travel from city A to city B. You can choose a “transportation strategy” based on your needs: take a fast train (speed focus), a comfortable bus (comfort focus), or a budget-friendly carpool (low-cost focus).

The remark about usage: This could lead to an increased number of classes and complexity when dealing with many strategies. Adding new classes and interfaces is optional if you have only a few algorithms that change rarely.

UML diagram of Strategy pattern:

An example in C# (rendering data in different formats based on user preference):

An example of the Strategy pattern in C#

The usage of the Strategy pattern would look like this:

Usage of the Strategy pattern

Observer

Usage: Maintain a consistent state by being notified of changes and, for example, notifying subscribers of events in a messaging system.

Real-world example: We can think of a breaking news app. Users subscribe to specific topics (sports, politics, etc.), acting as “observers.” When news breaks in a subscribed topic, the “observer pattern” notifies all relevant users with personalized alerts. Sports fans get their scores, and political enthusiasts receive election updates without them needing to check actively.

The remark about usage: This can result in performance issues when there are numerous observers or the update logic is complex. Subscribers are notified in an unpredictable sequence.

UML diagram of Observer pattern: An example in C#:

An example in C#:

The Observer Pattern in C#

The usage of the Observer pattern would look like follows:

The use of the Observer pattern

Command

Usage: Encapsulate a request as an object. For example, I implement undo/redo functionality in text or image editor.

Real-world example: Picture ordering food at a restaurant. You tell the waiter your wishes (pizza, extra cheese), creating an “order command.” The waiter then acts as a messenger, carrying your “command” to the Chef in the kitchen (receiver). The Chef, receiving the “command,” makes your pizza precisely as specified. This separation of ordering (command) from making (execution) lets you change or cancel easily.

The remark about usage: It has the potential to introduce complexity, as it requires creating additional classes for each action or request, complicating the architecture for simple operations.

UML diagram of Command pattern:

An example in C#:

The Command pattern in C#

The usage of the Command pattern would look like follows:

Usage of the Command pattern

State

Usage: Encapsulate state-specific behavior. For example, we are handling different states of a user interface element (e.g., enabled, disabled, selected).

Real-world example: The smartphone effortlessly transitions between states (on, off, silent, airplane mode) based on your actions. Each state (the “concrete state”) has unique behavior: on allows calls and notifications, silent mutes them, and airplane mode blocks signals. The phone (the “context”) doesn’t manage these behaviors directly; it delegates to the current state object. When you press a button or toggle a setting, the phone transitions to a new state, seamlessly changing its behavior without requiring intricate logic.

The remark about usage: It can lead to a proliferation of classes, as each state is typically represented by its class. This not only increases the complexity of the system but can also make it harder to manage and understand.

UML diagram of State pattern:

An example in C#:

The usage of the State pattern would look as follows:

Another interesting design pattern from this group is the Template method. It is used to define the skeleton of an algorithm in operation, deferring some steps to subclasses and implementing a base class for unit testing with customizable setup and teardown steps. Check the implementation in C#.

How to select the correct design pattern

Now, when we have analyzed all vital design patterns, we came up with the pattern we needed for our problem:

When selecting a design pattern

Here is the summary of the pattern selection:

Note: Design patterns are not the only kind of patterns we have. To learn more about other types of patterns, check here.

BONUS: Design Patterns Cheat Sheet

Download the PDF version.

Resources to learn more

Here are some more resources if you want to dig deeper into the world of design patterns, you can check the following resources:

Thanks for reading, and stay awesome!

To expand your knowledge and personal growth, subscribe to my free weekly newsletter with 25,000+ people: https://newsletter.techworld-with-milan.com.

Originally published at https://newsletter.techworld-with-milan.com on February 15, 2023.

--

--