5 Design Patterns That Are ACTUALLY Used By Developers

Alex Hyett - Sep 8 '23 - - Dev Community

High-level programming languages have been around since the 1950s and since then programmers worldwide have been using code to solve all sorts of different problems.

Over time, programmers realised that even though each problem might be unique, some problems and their solutions had similarities. They weren't exactly the same problem which meant they couldn't reuse code by creating libraries or algorithms to solve them.

Instead, they would reuse the main idea of the solution for similar problems that they came across. Eventually, names are given to these typical solutions and that is how the design patterns were born.

What is a design pattern?

In case you are struggling to understand the difference between a design pattern and an algorithm think of it this way.

Let's say it's my birthday tomorrow and my wife is going to make me a birthday cake. To do that she would probably follow a recipe that would tell her step-by-step what ingredients she needs to use and how to use them.

The recipe in this case is an algorithm that you can repeat over and over again to produce the same thing. Sure you might switch out or add in some different ingredients to make different cakes but they are mostly just different input variables.

As it is my birthday, I would also like someone to throw me a birthday party. If you imagine a birthday party in your head you are probably thinking of balloons, music, maybe a happy birthday banner and then the cake coming out with candles on it.

However, everyone's idea of a birthday party is going to be a little bit different. Some people might not like balloons, the music is going to be very different and the number of people invited will change.

Although if you had the superpower to be able to see what everyone was thinking you would probably still recognise it as a birthday party even though they are all very different.

The birthday party is a design pattern. It is the concept of a birthday party that is reusable even if everything else is slightly different.

What are the design patterns?

In 1994, 4 authors wrote down 23 design patterns in the book "Design-Patterns: Elements of Reusable Object-Orientated Software". This is obviously a bit of a mouthful, not something you can easily discuss at a birthday party so most people just call it the "Gang of Four book" or "GoF book" for short.

If you have been programming for a little while, even if you haven't learnt the design patterns yet you have probably already used some of them.

Most of them are just common sense. I know the first time I looked at the design patterns I recognised some of them as something I had used before even if I gave it a different name.

The design patterns in the book are split into 3 categories:

Creational

  • Abstract Factory
  • Builder
  • Factory Method
  • Prototype
  • Singleton

Structural

  • Adapter
  • Bridge
  • Composite
  • Decorator
  • Facade
  • Flyweight
  • Proxy

Behavioural

  • Chain of Responsibility
  • Command
  • Interpreter
  • Iterator
  • Mediator
  • Memento
  • Observer
  • State
  • Strategy
  • Template Method
  • Visitor

It is worth familiarising yourself at a basic level with these design patterns just so you have them at the back of your mind when you come to solving problems.

Out of all these design patterns, I am going to take you through the top 5 that I find myself using over and over again.

1. Strategy Pattern

Let's go back to the cake-baking analogy we had earlier. You love cake so much that you decide to write an application that gives you cake recipes.

You start off with the birthday cake. Then later you want to add red velvet cake and a carrot cake to your application. It is fine, maybe you just add in a switch statement and various if statements and change the process between them that way.

You then add in a coffee and walnut cake, a lemon cake and a chocolate fudge cake. Now your code is getting a bit hectic and looks more like spaghetti than cake.

Your application shouldn't need to care too much about which cake to use.

This is where the strategy pattern comes in.

You can split each of the cake recipes into their own class and have each one implement the cake-making strategy.

Now you just need to pick the correct strategy and run GetIngredients() and GetMethod().

This is really useful if you have multiple ways of achieving the same thing in your application. Maybe you have a navigation application and you need to switch between walking, cycling, driving or public transport.

It is a really useful pattern that stops your application from becoming a spaghetti mess of code.

2. Decorator Pattern

If you need to extend an object without changing the original implementation one of the ways to do that is using the decorator pattern.

I have mentioned the decorator pattern before in my video on SOLID, as it is one way that you can make something open to extension but closed to modification.

This is one of the patterns that I have used without realising it before. Whenever you create a wrapper around another class you are using the decorator pattern.

The way we normally do this is to have your decorator implement the same interface as the component you want to extend. Your decorator then takes the original component as an argument to the constructor.

You can then implement the original interface adding any additional functionality before or after calling the original method.

3. Observer Pattern

This pattern is another pattern that you are likely very familiar with even if you haven't knowingly used it before.

The observer pattern is used whenever you want to notify interested parties about something that has happened. If you are subscribed to my newsletter The Curious Engineer then every Sunday you will get an email from me. The email doesn't get sent to everyone who has ever visited my YouTube channel or read my newsletter but only to those select few who are subscribed.

This is how the observer pattern works. You have a publisher who implements the methods to let someone subscribe, unsubscribe and notify subscribers.

You then have a subscriber interface that each of the subscribers needs to implement in order for the publisher to be able to use it.

When a subscriber adds themselves to a publisher they get added to an array. When it comes to notifying the subscribers the publisher just needs to loop through the array calling the update method on the subscriber.

4. Singleton Pattern

When programmers first learn about global variables they think, "Great now I can declare something once and use it everywhere in my application". The problem is if you can access a global variable from everywhere you can also change it from everywhere.

There are cases though where you want to make sure that you only have one instance of an object. Typically whenever you have a shared resource like a database you don't want to create a new connection to the database every single time you need to use it.

The typical way to do this is to create a sealed class with a private constructor. Having a private constructor means that no other class can create a new instance of this class. You then create a static public method or property to create your instance which is stored in a static field.

public sealed class SimpleSingleton
{
    private SimpleSingleton() { }
    private static SimpleSingleton _instance;

    public static SimpleSingleton GetInstance(string value)
    {
        if (_instance == null)
        {
            _instance = new SimpleSingleton();
            _instance.Value = value;
        }
        return _instance;
    }

    public string Value { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

The problem with this implementation is that it is not thread-safe. Two different threads could run this code and both could evaluate the instance as null.

To get around this we need to implement some locking mechanisms in place such as this:

using System;
using System.Threading;

public sealed class ThreadSafeSingleton
{
    private ThreadSafeSingleton() { }

    private static readonly object _lock = new object();

    private static ThreadSafeSingleton _instance;

    public static ThreadSafeSingleton GetInstance(string value)
    {
        if (_instance == null)
        {
            lock (_lock)
            {
                if (_instance == null)
                {
                    _instance = new ThreadSafeSingleton();
                    _instance.Value = value;
                }
            }
        }
        return _instance;
    }

    public string Value { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Or we can just make use of the Lazy type added in .NET 4.

public sealed class LazySingleton
{
    private LazySingleton() { }
    private static readonly Lazy<LazySingleton> _lazy = new Lazy<LazySingleton>(() => new LazySingleton());

    public static LazySingleton GetInstance(string value)
    {
        var instance = _lazy.Value;
        instance.Value = value;

        return instance;
    }

    public string Value { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

5. Facade Pattern

This pattern is essential to software development and everyday life chances are you have been using it already.

The facade pattern is all about simplicity. We are constantly making use of different frameworks and libraries and these are great time savers but they can also overcomplicate our code. Many of these libraries do a lot more than what we need them to do.

Let's say you are making use of a fictional logging library that isn't very well designed. This library can log to the console, to a file or even send you a Slack message. The library only has one method Log and it is up to you to specify all the inputs of where you want to log and how you want to log it.

Rather than clutter up your code with all of these implementation details you can create your own logger that has nice clean methods that you can use throughout your application. All of this messy code that you need to implement is now restricted to this one class. If you find a better logger to use in the future you can just switch it out but keep the same interface and the rest of your code doesn't need to change.

Final Thoughts

These are the 5 design patterns that I use regularly and I know a lot of other developers do too. It is still worth having a look at the other 18 patterns so you are more familiar with them. It really helps to have the design patterns in the back of your mind when you are trying to come up with solutions.

There is a great website called refactoring.guru that does a fantastic job of explaining the 23 design patterns from the book and gives some good examples of when to use them. It is definitely worth bookmarking so you can reference it later on.


📨 Are you looking to level up your skills in the tech industry?

My weekly newsletter is written for engineers like you, providing you with the tools you need to excel in your career. Join here for free →

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .