Top 6 Design Patterns Every Developer Should Know
They are the proven and most reusable solutions to common problems in software design. Therefore i think , we all must be aware of atleast most common used patterns so that we dont have to reinvent the wheel every time we encounter a common problem. They are like templates of our code, we follow the idea behind them.
In simple terms, it's like recipe for solving smart ways.
They are mainly divided into the 3 categories based on the type of problem they solve :
Creational patterns : " How to create objects ", its main goal is to make object creation easier, controller and independent from the exact classes.
Structural patterns : " How to organize objects ", its main goal is to make large codebases easier to structure, extend, and maintain.
Behavioral patterns : " How should objects interact and communicate ", its main goal is to Improve communication and responsibility sharing between objects.
We further categories those patterns in subcategories. Since, i've explained the implementation i'll be using word types instead of classes.
1. Singleton Pattern (Global single access)
Imagine a situation where we want only a single object of a class in whole app because creating multiple instances of that object would be redundant or may cause inconsistent behavior(due to concurrent access). This problem is usually occurs in resource management, like database management, caches etc. So, in those scenarios we consider our instance creation to centralize.
Its a creational design pattern, ensures that only one instance of type is created and used throughout the application. It provides global access point of that instance.
Real life analogy
Database Connection Pool : We'll certainly dont want to have multiple database connection for a single application. Instead of getting a new connection we receive a connection that is already created once.
Logger : In large applications we usually have one logger that write logs to a file and instance of that single logger is shared though out the application.
The Singleton pattern solves two problems at the same time:
Ensure that a class has just a single instance
Provide a global access point to that instance
Template for using Singleton in GO
Define the Singleton Struct : This is the actual object we want to only have one instance of.
Declare Instance & using sync.Once (synchronization lib) : we declare a pointer to hold our instance and use sync.Once, its concurrency feature ensures that a function runs only once even in multi threaded environment.
Create a Global Getter Function: we create a function that is the only way to create that single instance. We use once.do to run out code once and store the instance in our global pointer.
2. Factory Pattern ( Object creation abstraction )
Suppose we are building a web application, which requires user to login via google, facebook, github etc. Each of login method requires different auth flows. If we handle all of this in a single main logic using if else then it will become messy and hard to extend and will violates the open/closed principle. To tackle this we need to handle the creation of particular object via a factory function. Here we are abstracting the creation of object. User is unaware about the creation logic.
Factory is a creational design pattern, that create objects without showing the creation logic to the user. The user asks for the object by type, factory give it one. Instead of calling directly constructor of that object, we delegate it to the factory function.
Real life analogy
if we go to a factory to buy car, we dont care how it's built. Manufacturing is handled by the factory itself. Factory just provides us the desired model.
How to use factory pattern in GO ?
We use interfaces and factory function together. ( interfaces + one central function )
Define an interface : Create an interface that represents common behavior of multiple types.
Implement that interface : Create all types that implement the interface.
Write the Factory Function : This function returns the correct type based on input provided by the user.
Benefits of using factory
Encapsulate object creation logic : we hide the construction detail from client.
Promotes loose coupling : Since client code depends upon interface. We can always change or replace the implementation.
Enables runtime decision making : We can choose objection creation, based upon the runtime environment or configuration.
3. Strategy Pattern (Behavioral algorithm switch)
Imagine we have an E-commerce app that is being used in different parts of the world. For that we might need a tax calculator to apply different types of taxes on sales like GST, VAT etc. So, we need a particular behavior at run time. Either we need to use if else in main logic to apply particular tax but this will violates open/ closed principle. Since adding the new behavior will make code messier and hard to maintain. Or other option is to abstract the tax calculation behavior.
It's a behavioral design pattern that lets us define a family of behaviors ( algorithms), put each of them in separate types and make them interchangeable at runtime without changing the actual object that is using them.
How to use strategy pattern in Go ?
Abstract the common behavior using interface and use composition in context class ( to use run time behavior ).
Define the Strategy Interface : Common behavior that we want to change dynamically.
Create Concrete Strategy types : These will be different behaviors (algorithms) implementing the interface.
Create the Context type : This type that uses our concrete strategies by using composition over on an interface and we use a setter function to set particular strategy.
4. Observer Pattern ( Event + Automatic Reaction )
In real world, when an order is placed on an ecommerce app. Multiple events needs to occured automatically, one of them is to notify the user via multiple ways eg. email, SMS or maybe push notification. For that either we need to manually call each notification service. But that will tightly couples the even source ( orderService, stockService ) with the event consumers ( emailNotifier, pushnotifier). Adding a new subscriber of event source requires modifying existing code. Hence violating the open/ closed principle.
So there, we need observer pattern to automatically notify all registered observers.
It's a behavioral design pattern, that lets us define a one to many relationship between objects. So that one object changes, all its dependent objects are automatically notified and updated.
How to use observer pattern in Go ?
Consider a scenario, we are building a weather station (Subject) that notifies multiple devices (Observers) whenever the temperature changes.
Define the Observer Interface : We need a common contract that all observers (Phone, TV, etc.) will implement so the Weather Station knows how to notify them.
Define the Subject Interface (Who triggers notifications) : Types that are implementing above common behavior (observer interface).
Create the Subject : This is the actual object being observed. It holds the current state ( temperature) and a list of registered observers.
Implement Concrete Observers : These are the receivers of updates. They each define what to do when the temperature changes.
5. Decorator Pattern ( Dynamic Behavior Extension )
In today's most of the modern software requires logging system that is flexible to use depending upon the need of logs required to logged. For eg. sometimes we just want to print a log, but sometimes we want to add timestamps or add log level or maybe writing it to a log file. So we dont know in advance, which combination of feature will be needed. But we want to system to be flexible for future changes.
The straight forward way is to create multiple classes as the new features are needed. But this lead to have too many classes. That will be over engineering and make code harder to read, harder to change, and sometimes slower. So here comes the decorator pattern
It's a structural pattern, that let us add new features to an object without changing its code by wrapping it inside in other object that add those features.
Think of it like:
We have a basic object.
We can wrap it in layers (decorators).
Each layer adds something new—like a timestamp, logging level, formatting, etc.
The original object stays untouched.
Real life analogy
Suppose we have a basic pizza with a base price
Now, we can always add the cheese, extra toppings -> each increase the price without effecting the base pizza
Similarly we add features (ingredients) dynamically without changing (base pizza) original code.
How to implement it in Go ?
Suppose , we’re building a simple logging system where:
The basic logger just prints messages.
Sometimes we want to add a timestamp to the log.
Sometimes we want to add log levels like INFO, ERROR.
The goal is to dynamically add features without changing the core Logger.
Create a Common Interface (Logger) : A shared contract so that all loggers and decorators can be used interchangeably. It makes it possible to treat all loggers same way.
Create the Basic Concrete Logger : This is original object without any decorator and implements the interface . Every decorator wrap this object as a base.
Create the Decorator(s) ➔ Each One Adds a Feature : Each decorator struct wraps another Logger (via composition) and adds something new before/after calling the wrapped logger’s Log.
Stack Features Dynamically at Runtime : We can choose which decorators to apply and in what order without changing any existing type. for eg.
TimestampDecorator ➔ LevelDecorator ➔ ConsoleLogger ➔ Print
Each layer Adds something (timestamp, level, etc.)
Then calls the inner layer's Log method.
6. Builder Pattern ( Stepwise Object Construction )
Sometimes, we have an object that have too many fields for eg a HTTP request object where some fields are mandatory ( URL, request method etc. ) but some fields are optional ( header, query param, timeout, cookies ). But object must be prepared before the request is sent, if we tried to create object using a single constructor called telescoping constructor leads to mess and error - prone code. But with the help of builder pattern we construct complex object step by step.
Its an creational pattern helps you create complex objects step-by-step, letting us set only what matters, in any order, and ending with a valid object.
How do we implement it in GO ?
The Builder Pattern commonly uses method chaining to allow the user to set multiple optional fields in a readable, step-by-step way. This is called a fluent interface. When implementing the builder pattern, we follow some steps.
Define the Main Object : it's the main complex object that we want to simplify its creation.
Create the Builder Struct : This struct will hold the main object via composition while it’s being built.
Create a Constructor for the Builder : It will ensures the mandatory fields are set from the beginning.
Add Chained Setter Methods for Optional Fields : It sets one field at a time. Return the builder itself enable chaining.
Add the Build Method to Get the Final Object : This returns the fully, valid object.
Real life examples of builder pattern
SQL Query Builders : Without Builder, complex SQL queries would become unreadable strings.
UI Component Builders : Helps build UI components with multiple optional parameters
Software Engineer @VYTURR | Ex-Chat360 GoLang, Python & Ruby on Rails | Backend Engineer | DevOps & GenA.i
1moVery informative