Software Design Patterns

Hashan Kogul Yogendran
13 min readAug 28, 2023

--

Introduction

A Software Design Pattern is a general, reusable solution to a commonly occurring problem within a given context in software design.

It is not a finished design that can be transformed directly into source or machine code. Rather, it is a description or template for how to solve a problem that can be used in many different situations.

Design patterns are formalized best practices that the programmer can use to solve common problems when designing an application or system.

Design patterns are templates to solve common problems in Software engineering. They provide repeatable solutions, which you can apply to some of the common problems you come across.

Design patterns are not finished solution or a code or a class or a library that you can import in your project. They are a generic solution for solving a problem. Each project will have a slightly different way to solve it.

Why should you learn Design patterns?

Why would you even consider learning Design patterns? You can write codes even without them. I can give you a few reasons why

  • Something you need to know to be a better Software Engineer — Design patterns can help you be a better Software Engineer. In computer programs, very often some of the complex problems can be solved easily and elegantly by the use of Design patterns. Just by knowing about the Design patterns and knowing how to apply one, you can save a lot of time and improve the overall quality of code you deliver. All of this will help us grow and become a better Software engineer.
  • Solutions already exist — Most of the problems you might encounter in your life, writing codes are not new. There has already been a bunch of people who have seen that problem, worked on it for days and came up with a solution and most likely written about it somewhere. You can use their knowledge and experience. You do not need to build the solution from scratch. You only have to learn to identify them.
  • Improve communication of idea using common vocabulary — You can easily convey your idea by just using the Design pattern name instead of describing all the technical details. It helps you in communicating the idea clearly.
  • The standard way to solve a problem — Design patterns provide a tried and tested way to solve a problem. Using these tried and tested approach to solve a problem will speed up your overall development process. Using a design pattern in your project will also help you uncover issues which you might not have uncovered until very late had you not used it.
  • Code readability — Design patterns also improves code readability amongst the people familiar with the Design patterns.

Types of Design Patterns

1. Creational Patterns

Creational patterns focus on object creation mechanisms, abstracting the instantiation process and promoting decoupling between clients and the types of objects they create. Let’s explore some prominent creational patterns:

  • Singleton Pattern: This pattern ensures a class has only one instance and provides a global point of access to that instance. It’s often used for settings, logging, and database connections.
  • Factory Method Pattern: The factory method pattern defines an interface for creating objects but allows subclasses to decide which class to instantiate. This promotes flexibility and extensibility in object creation.
  • Abstract Factory Pattern: Building on the factory method pattern, the abstract factory pattern provides an interface for creating families of related or dependent objects. It’s suitable for situations where multiple object types need to be created together.
  • Builder Pattern: When constructing complex objects step by step, the builder pattern separates the construction process from the actual representation, allowing different representations to be created using the same construction process.
  • Prototype Pattern: This pattern involves creating new objects by copying an existing object, known as the prototype. It’s useful when object creation is expensive, and multiple similar instances are required.

2. Structural Patterns

Structural patterns focus on the composition of classes and objects to form larger structures while keeping these structures flexible and efficient. Let’s explore a few key structural patterns:

  • Adapter Pattern: The adapter pattern allows incompatible interfaces to work together. It converts the interface of one class into another interface clients expect, facilitating collaboration between different components.
  • Decorator Pattern: This pattern dynamically adds responsibilities to objects without altering their structure. It’s like adding layers of functionality to an object, making it highly adaptable and extensible.
  • Facade Pattern: The facade pattern provides a unified interface to a set of interfaces in a subsystem. It simplifies complex systems by offering a high-level interface that hides the underlying complexity.
  • Bridge Pattern: The bridge pattern separates abstraction from implementation. It allows both to evolve independently while maintaining a connection between them. This is particularly useful when changes in one aspect should not affect the other.
  • Composite Pattern: This pattern composes objects into tree structures to represent part-whole hierarchies. It allows clients to treat individual objects and compositions of objects uniformly.
  • Flyweight Pattern: When dealing with a large number of objects with shared characteristics, the flyweight pattern reduces memory usage by sharing common state between objects.

3. Behavioral Patterns

Behavioral patterns focus on communication between objects, defining how they collaborate and interact. Let’s explore a selection of behavioral patterns:

  • Observer Pattern: This pattern establishes a dependency between objects so that when one object changes state, its dependents are notified and updated automatically. It’s commonly used in event handling.
  • Strategy Pattern: The strategy pattern defines a family of interchangeable algorithms and allows them to be selected and used at runtime. It promotes flexibility by enabling dynamic algorithm selection.
  • Command Pattern: This pattern turns a request into a stand-alone object containing all the information about the request. This decouples sender and receiver, allowing for parameterized and delayed method invocation.
  • Chain of Responsibility Pattern: When multiple objects might handle a request, the chain of responsibility pattern creates a chain of handler objects, each passing the request along the chain until it’s handled or reaches the end.
  • State Pattern: This pattern allows an object to change its behavior when its internal state changes. It’s particularly useful for implementing state machines.
  • Interpreter Pattern: When dealing with a language or grammar, the interpreter pattern defines a representation for the grammar and provides an interpreter to interpret sentences in the language.
  • Visitor Pattern: The visitor pattern lets you add further operations to objects without having to modify them. It separates operations from the objects they operate on.
  • Mediator Pattern: When objects need to communicate through a central mediator, the mediator pattern defines an object that encapsulates how objects interact, promoting loose coupling.

Real-World Applications

Let’s explore some real-world scenarios where software design patterns shine :)

  • E-commerce Platform: The abstract factory pattern could be employed to create families of related objects such as different payment gateways and corresponding UI components.
  • Text Editor: The command pattern could be used to implement undo and redo functionalities, allowing users to revert changes and navigate through their editing history.
  • Game Development: The observer pattern could be applied to implement event handling, notifying game components about changes in game state.
Real World Example — Factory System

Commonly used Design Patterns

  • Singleton Pattern — There can only be one instance of a given class. This is one of the most used/common design patterns. Singleton solves the problem of ensuring only one instance of a class. This pattern is generally used with other patterns like Facade, Factory & etc. Because we want only one instance of those classes.
  • Strategy Pattern — This design pattern allows you to select algorithm/logic at runtime. For example, your program needs to pick the kind of Sorting algorithm it needs to use based on some logic.
  • Adapter Pattern — This design pattern works to connect two incompatible interfaces, that cannot be connected. It wraps an existing class in a new interface to make it compatible with client interface.
  • Factory Pattern — This pattern allows the creation of objects without having to specify the exact class of the object.
  • Proxy Pattern — In the Proxy pattern, a class functions as an interface to ‘something’. This ‘something’ could be a network request to get JSON response from a REST API or could be getting the JSON response from the locally cached Database.

There are several reasons why design pattern will be beneficial to you.

Simply put, design patterns are used to make the task easier.

— For instance, when writing a program, the primary rule of an object is that all attributes must be private and cannot be accessed by other classes. So, how can we make our code more readable and adaptable? You can utilize the Creational Design Pattern to clean up your code.

— Another reason is that design patterns establish a shared vocabulary that you and your colleagues may use to communicate more effectively. You can remark, “Oh, just use a Singleton for it, though,” and everyone will comprehend your advice. If you already know the pattern and its name, there’s no need to explain what being a singleton is.

I have researched many design patterns, and I’ve used some of them in personal and work projects.

So, in this piece, I’ll share my top three design pattern recommendations, all of which are simple to learn and use in your work/personal projects.

So, get a cup of coffee and enjoy reading!

3 Design Patterns Every Developer Should Learn

1. Strategy Design Pattern

This pattern enables you to switch between techniques for a specific task at runtime without the client knowing it. Rather than implementing a single method directly, the code is given runtime instructions that tell it which of the group of algorithms to run.

The “Open-Closed design principle,” which argues that base code should be open for extension but closed for modification, was one of the vital principles I learned about in my fourth year of university (which I’m sure many of you have heard of).

One way to achieve the open-closed principle is to use the strategy pattern.

When numerous algorithms for a given strategy (interface) are required, this pattern comes in handy. This pattern’s implementation necessitates four elements, including the client:

— Client -> This is the place when the context is used.

— Context -> the situation in which an algorithm is chosen at runtime.

— interface -> strategy

— Algorithms -> real-world applications

Let’s have a look at an example.

Assume you’ve created an app for a business that allows you to mail packages to nearby clients. The app’s first edition included bike delivery, which proved to be a huge hit. Delivery by bike is no longer a flimsy alternative when your company expands across the city.

The business has now requested that you create a function that allows package delivery by car. That was just the start.

You were later instructed to add rail and, later still, air. The Package delivery class grows in size and gets more challenging to manage as new algorithms are added. Any minor flaw in the class can jeopardize the functional code, necessitating re-testing of the entire app, even if the problem was only with one of the algorithms.

As a result, the strategy pattern comes to the rescue, stating that these algorithms should be separated into independent classes called strategies. So, you won’t have to change the main course the next time you wish to add a ship as an option.

The pseudocode below shows how the main class employs these tactics to transmit packages in various ways.

interface process PackageStrategy has 
method processPackage(package);

//These strategies implement the algorithm by implementing the interface above.
class SendByRail implements process PackageStrategy has
method processPackage(package) {
//process the package that will be sent by Rail and return something
}
class SendByBike implements process PackageStrategy has
method processPackage(package) {
// process and return something
}
//strategies used by the context class
class Context {
private strategy: processPackageStrategy

method setStrategy(processPackageStrategy Strategy) does
this.strategy= strategy;

method executeStrategy(Package package) does
return strategy.processPackage();
}

// read that strategy from user (UI or Api)
class App {
create a new context instance;
get package info;
read the desired user choice

if (choice is rail) then
context.setStrategy(new SendByRail())

if (choice is bike) then
context.setStrategy(new SendByBike())

response = context.executeStrategy(package)
//do something with response.

2. Singleton Design Pattern

There is only one instance of a class.

The singleton pattern is used when only one instance of a class is required. The primary motive for limiting the instantiation of a class is to maintain control over shared resources such as databases, stores, and files. With this technique, we construct a class instance and give that instance global access.

For anything launched with an API key, I’ve used a singleton as the source of configuration settings for a client-side web app, as well as for holding data in memory in a client-side web application using flux.

Because singletons do not need a state, they are used to implement comparators. Because we are just constructing a single model, we can conserve memory.

To give you an idea, here’s a JavaScript snippet of code I wrote as an example:

class Store {
static instance;
constructor() {
if (!Store.instance) {
this._state = [];
Store.instance = this;
}
return Store.instance;
}
add(stuff) {
this._state.push(stuff);
}
}

const instance = new Store();
Object.freeze(instance);

export default instance;

// In other files
const a = new Store();
a.add("phone");

const b = new Store();
console.log(b._state) // outputs ["phone"] - shared state

Many of us and many of you may have been limiting the instantiation of a class to one for various reasons and have only now realized that it was a design pattern all along!

3.Observer Design Pattern

I’ll give you a bird’s-eye view of this pattern!

The one-to-many relationship between the numerous objects is the basis for this design pattern. It enables you to set up a subscription mechanism that permits other entities to be alerted of each occurrence on the entity to which you are subscribed

Kafka, RabbitMQ, Amazon SNS, and NATS are some real-world examples of pub/sub systems that implement the publisher/subscriber pattern (a variant of the Observer!).

The following are some examples of this pattern:

In the world of web development, particularly with React, you’ve heard of Redux for managing your application’s state. Redux is the implementation of the Observer pattern.When you attach an action to update the state in the store, components listening to the change will adjust their representations accordingly.

If code were contributed to a remote repository, a CI environment would monitor it for changes and execute a build.

The event-driven procedural programming is used to mimic the Observer design pattern. Like other design patterns, this pattern allows us to define loosely linked systems. We can develop maintainable and modular software using this technique. We can also achieve obvious segmentation between distinct actors in an event-driven system.

These are some of the commonly used ones. GoF came up with 23 design patterns in total in their book. Following is the complete list as categorized by them.

Is using design patterns always a good idea?

Although the design patterns are widely acknowledged to provide considerable advantages. When it is overused, however, it has undesirable side effects.

Therefore, developers should consider whether using design patterns is efficient and appropriate for the project, and look for alternative solutions and compare them to design patterns. Nonetheless, every software engineer should know about design patterns, their many forms, and the most popular ones.

Design Patterns’ Beginnings!

If you’re curious about the origins of design patterns, this section is for you. So keep reading!

Architect Christopher Alexander pioneered the idea of design patterns, which he discussed in his book “The Timeless Way of Building.” If you want to know what’s going on, I recommend reading it straight from the source. It was followed by “A Pattern Language,” an excellent guide to how patterns should be designed. Please don’t take my word for it; look it up for yourself.

These ideas picked up steam over the next few years. In 1994, the Crew of Four (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides), a group of software engineering researchers, published Design Patterns: Elements of Reusable Object-Oriented Software, which is still on several software engineer’s bookshelves.

This book is widely regarded as the “public debut” of design patterns in the software development community, and it has influenced the progress of design patterns ever since.

Conclusion

Software design patterns arise as guiding lights of organization and beauty in the dynamic world of software development, where problems are as varied as the solutions. In addition to offering solutions to common design issues, these patterns help to build a community of best practices that are independent of time and technology. You have gained a glance into the toolbox of the architect, a toolkit full of techniques for developing software that withstands the test of time, as you have traveled through the world of creational, structural, and behavioral patterns.

Thank you

Thank you for embarking on this exploration of software design patterns. Your dedication to enhancing your software craftsmanship is a testament to your passion for excellence. As you continue your software development journey, armed with the knowledge of these patterns, may you find joy in the art of creating, and may your code resonate with the wisdom of the patterns that have shaped our digital landscape.

Happy coding, and may your software always be a masterpiece of ingenuity and innovation.

With gratitude,

Hashan Kogul Yogendran

--

--