GoodLookRetail BlogEmber
Design

Design Patterns - Core Concepts

System Design Essentials: What Every Developer Should Learn

N
Neeraj Sachan
27 June 2026 · 13 min read
25 Views0 Comments13 Min Read
Design Patterns - Core Concepts

SOLID Principles

·        S   - Single-responsibility Principle

·        O  - Open-closed Principle

·        L   - Liskov Substitution Principle

·        I   - Interface Segregation Principle

·        D - Dependency Inversion Principle

S — Single

Responsibility Principle (S.R.P)

A class should have one, and only one, reason to change. Also class should have one responsibility.

O — Open-Closed Principle

You should be able to extend a classes behavior, without modifying it.

This principle is the foundation for building code that is maintainable and reusable.

  • Open for extension

This ensures that the class behavior can be extended. As requirements change, we should be able to make a class behave in new and different ways, to meet the needs of the new requirements.

  • Closed for modification

The source code of such a class is set in stone, no one is allowed to make changes to the code.

How do we achieve this?

Through abstractions.

Example: Our banking application supports two account types – “current” and “savings”. These are represented by the classes CurrentAccount and SavingsAccount respectively.

The BankingAppWithdrawalService serves the withdrawal functionality to its users:

image.png

Unfortunately, there is a problem with extending this design. The BankingAppWithdrawalService is aware of the two concrete implementations of account. Therefore, the BankingAppWithdrawalService would need to be changed every time a new account type is introduced.

Let's redesign the solution to comply with the Open/Closed principle. We'll close BankingAppWithdrawalService from modification when new account types are needed, by using an Account base class instead:

image.png

Here, we introduced a new abstract Account class that CurrentAccount and SavingsAccount extend.

The BankingAppWithdrawalService no longer depends on concrete account classes. Because it now depends only on the abstract class, it need not be changed when a new account type is introduced.

Consequently, the BankingAppWithdrawalService is open for the extension with new account types, but closed for modification, in that the new types don't require it to change in order to integrate.

 

L — Liskov Substitution Principle

Derived classes must be substitutable for their base classes.

There is a problem in above code. As Fixed deposit is not permitted to withdraw. So it breaks LSP principle. Let’s refactor..

image.png

Because all accounts do not support withdrawals, we moved the withdraw method from the Account class to a new abstract subclass WithdrawableAccount. Both CurrentAccount and SavingsAccount allow withdrawals. So they've now been made subclasses of the new WithdrawableAccount.

This means BankingAppWithdrawalService can trust the right type of account to provide the withdraw function

 

I — Interface Segregation Principle

Make fine grained interfaces that are client specific.

Clients should not be forced to implement interfaces they do not use.

Interface segregation simply means that we should break larger interfaces into smaller ones. The goal of this principle is to reduce the side effects of using larger interfaces by breaking application interfaces into smaller ones. It's similar to the Single Responsibility Principle, where each class or interface serves a single purpose.

D — Dependency Inversion Principle

Depend on abstractions, not on concretions.

It is based on the Open/Closed Principle and the Liskov Substitution Principle. You should, therefore, at least be familiar with these two principles, before you read this article.

A. High level modules should not depend upon low level modules. Both should depend upon abstractions.

B. Abstractions should not depend upon details. Details should depend upon abstractions.

In English, Abstraction is not concrete.

An important detail of this definition is, that high-level and low-level modules depend on the abstraction. The design principle does not just change the direction of the dependency, as you might have expected when you read its name for the first time. It splits the dependency between the high-level and low-level modules by introducing an abstraction between them. So in the end, you get two dependencies:

The high-level module depends on the abstraction, and the low-level depends on the same abstraction.

image.png

Key points:

Dependency Injection is an implementation of Dependency Inversion Principle.

One of the ways to achieve Open-Close Principle is to use Dependency Inversion Principle.

High-level modules in DIP are modules that appear on the higher part of the UML diagram and depends on the abstraction layer. Abstractions are the ones in the center of the diagram. Low-level modules are the ones in the lower level of the diagram and are the actual implementations of the abstraction layer.

Gang of Four Design Patterns

Creational Design Patterns

o  Abstract Factory- Allows the creation of objects without specifying their concrete type.

o  Builder- Uses to create complex objects.

o   Factory Method- Creates objects without specifying the exact class to create.

o   Prototype- Creates a new object from an existing object.

o   Singleton- Ensures only one instance of an object is created.

Structural Design Patterns

o  Adapter- Allows for two incompatible classes to work together by wrapping an interface around one of the existing classes.

o  Bridge- Decouples an abstraction so two classes can vary independently.

o  Composite- Takes a group of objects into a single object.

o  Decorator- Allows for an object’s behavior to be extended dynamically at run time.

o  Facade- Provides a simple interface to a more complex underlying object.

o  Flyweight- Reduces the cost of complex object models.

o  Proxy- Provides a placeholder interface to an underlying object to control access, reduce cost, or reduce complexity.

Behavior Design Patterns

o   Chain of Responsibility. Delegates commands to a chain of processing objects.

o   Command. Creates objects which encapsulate actions and parameters.

o   Interpreter. Implements a specialized language.

o   Iterator. Accesses the elements of an object sequentially without exposing its underlying representation.

o   Mediator. Allows loose coupling between classes by being the only class that has detailed knowledge of their methods.

o   Memento. Provides the ability to restore an object to its previous state.

o   Observer. Is a publish/subscribe pattern which allows a number of observer objects to see an event.

o   State. Allows an object to alter its behavior when its internal state changes.

o   Strategy. Allows one of a family of algorithms to be selected on-the-fly at run-time.

o   Template Method. Defines the skeleton of an algorithm as an abstract class, allowing its sub-classes to provide concrete behavior.

o   Visitor. Separates an algorithm from an object structure by moving the hierarchy of methods into one object.

The Factory pattern deals with creating objects of a single type, while the Abstract Factory pattern deals with creating objects of related types.

Factory pattern:

We create object without exposing the creation logic to the client and refer to newly created object using a common interface.

Abstract Factory

In Abstract Factory pattern an interface is responsible for creating a factory of related objects without explicitly specifying their classes.

Each generated factory can give the objects as per the Factory pattern.

Singleton pattern

This pattern involves a single class which is responsible to create an object while making sure that only single object gets created.

Builder pattern

Builder pattern builds a complex object using simple objects and using a step by step approach.

Ex: making Meal

Prototype

Prototype pattern refers to creating duplicate (clone) object while keeping performance in mind.

Adapter pattern

Adapter pattern works as a bridge between two incompatible interfaces. This pattern involves a single class which is responsible to join functionalities of independent or incompatible interfaces.

image.png

Bridge

Bridge is used when we need to decouple an abstraction from its implementation so that the two can vary independently.

This pattern involves an interface which acts as a bridge which makes the functionality of concrete classes independent from interface implementer classes. Both types of classes can be altered structurally without affecting each other.

Before and After

   

image.pngimage.png

 

Filter pattern

Filter pattern or Criteria pattern is a design pattern that enables developers to filter a set of objects using different criteria and chaining them in a decoupled way through logical operations.

Composite pattern

Composite pattern is used where we need to treat a group of objects in similar way as a single object. Composite pattern composes objects in term of a tree structure to represent part as well as whole hierarchy.

Composite design pattern allows you to have a tree structure and ask each node in the tree structure to perform a task. You can take real life example of a organization. It has general managers and under general managers, there can be managers and under managers there can be developers. Now you can set a tree structure and ask each node to perform common operation like getSalary().

As described by Gof:

"Compose objects into tree structure to represent part-whole hierarchies. Composite lets client treat individual objects and compositions of objects uniformly".

  • Compose objects into tree structures to represent whole-part hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.

  • Recursive composition

  • "Directories contain entries, each of which could be a directory."

  • 1-to-many "has a" up the "is a" hierarchy

Composite design pattern treats each node in two ways-Composite or leaf.Composite means it can have other objects below it.leaf means it has no objects below it.

Tree structure:

image.png

Composite(Company Directory)

Leaf(Manager)

Leaf(Developer)

Component(Employee) (manager, developer all are employees )

Decorator pattern

Decorator pattern allows a user to add new functionality to an existing object without altering its structure. We use inheritance or composition to extend the behavior of an object but this is done at compile time and it’s applicable to all the instances of the class. We can’t add any new functionality of remove any existing behavior at runtime – this is when Decorator pattern comes into picture.

The decorator design pattern allows us to dynamically add functionality and behavior to an object without affecting the behavior of other existing objects in the same class.

We use inheritance to extend the behavior of the class. This takes place at compile time, and all of the instances of that class get the extended behavior.

Interface: Shape

Concrete class -> Circle, Rectangle

till here it is good. Now I want to decorate circle or rectangle class. (I should be able to fill color into this)

So decorator design comes here.

Now create a ShapeDecorator asabstract class implements Shape Interface

public abstract class ShapeDecorator implements Shape {

      protected Shape decoratedShape;

      public ShapeDecorator(Shape decoratedShape) {

            super();

            this.decoratedShape = decoratedShape;

      }

}

Now Create the FillColorDecorator to add the functionality of the fill-color in the shape.

public class FillColorDecorator extends ShapeDecorator {
      protected Color color;
      public FillColorDecorator(Shape decoratedShape, Color color) {
            super(decoratedShape);
            this.color = color;
      }

// implements all methods of ShapeDecorator( aka   Shape)

      @Override
      public void draw() {
            decoratedShape.draw();
            System.out.println("Fill Color: " + color);
      }

Facade pattern

In Java, the interface JDBC can be called a facade because, we as users or clients create connection using the “java.sql.Connection” interface, the implementation of which we are not concerned about. The implementation is left to the vendor of driver.

Facade pattern hides the complexities of the system and provides an interface to the client using which the client can access the system

This pattern involves a single class which provides simplified methods required by client and delegates calls to methods of existing system classes.

image.png

Flyweight pattern

One difference in that flyweights are commonly immutable instances, while resources acquired from the pool usually are mutable.

So you create flyweights to avoid the cost of repeatedly create multiple instances of objects containing the same state (because they are all the same, you just create only one and reuse it throughout all places in your app), while resources in a pool are particular resources that you want to control individually and possibly have different state, but you don't want to pay the cost of creation and destruction because they are all initialized in the same state.

Flyweight pattern is primarily used to reduce the number of objects created and to decrease memory footprint and increase performance.

Maintain the object created into some short of Cache.

private static Map<Color, Vehicle> vehiclesCache = new HashMap<>();

 

public static Vehicle createVehicle(Color color) {

    Vehicle newVehicle = vehiclesCache.computeIfAbsent(color, newColor -> {

        Engine newEngine = new Engine();

        return new Car(newEngine, newColor);

    });

    return newVehicle;

}

In programming, we can see java.lang.String constants as flyweight objects. All strings are stored in string pool and if we need a string with certain content then runtime return the reference to already existing string constant from the pool – if available.

Proxy pattern

In proxy pattern, a class represents functionality of another class. In the real work a cheque or credit card is a proxy for what is in our bank account. So it's quite a simple concept - to save on the amount of memory used, you might use a Proxy. Similarly, if you want to control access to an object, the pattern becomes useful. Proxy pattern is also known as Surrogate or Placeholder. A proxy object hides the original object and control access to it.

1.      We can use proxy when we may want to use a class that can perform as an interface to something else.

2.      Proxy is heavily used to implement lazy loading related use cases where we do not want to create full object until it is actually needed.

3.      A proxy can be used to add an additional security layer around the original object as well.

image.png

Chain of responsibility

As the name suggests, the chain of responsibility pattern creates a chain of receiver objects for a request. This pattern decouples sender and receiver of a request based on type of request.

Command Pattern(Behavioral)

A request is wrapped under an object as command and passed to invoker object. Invoker object looks for the appropriate object which can handle this command and passes the command to the corresponding object which executes the command.

Thread runnable run method.

Runnable task is wrapped object.

interface Command{

    public void execute();

}

 

class Light{

    public void on(){

        System.out.println("Light is on");

    }

    public void off(){

        System.out.println("Light is off");

    }

}

class LightOnCommand implements Command

{

}  

class LightOffCommand implements Command

{

}

Interpreter pattern (Behavioral)

Interpreter pattern provides a way to evaluate language grammar or expression. This pattern involves implementing an expression interface which tells to interpret a particular context. This pattern is used in SQL parsing, symbol processing engine etc.

Iterator Pattern (Behavioral)

This pattern is used to get a way to access the elements of a collection object in sequential manner without any need to know its underlying representation.

Mediator pattern (Behavioral)

Mediator pattern is used to reduce communication complexity between multiple objects or classes. This pattern provides a mediator class which normally handles all the communications between different classes and supports easy maintenance of the code by loose coupling.

One could say that an ESB (Enterprise Service Bus) is essentially a large-scale application of the Mediator pattern.

Memento pattern (Behavioral)

Memento design pattern is used when we want to save the state of an object so that we can restore later on. Memento pattern is used to restore state of an object to a previous state. Memento pattern falls under behavioral pattern category.

Text Editor.

image.png

Observer pattern (Behavioral)

Observer pattern is used when there is one-to-many relationship between objects such as if one object is modified, its dependent objects are to be notified automatically.

Event Handling. Observer, Observable.

State pattern

State pattern, we create objects which represent various states and a context object whose behavior varies as its state object changes. State design pattern is used when an Object changes its behavior based on its internal state.

Suppose we want to implement a TV Remote with a simple button to perform action. If the State is ON, it will turn on the TV and if state is OFF, it will turn off the TV.

The benefits of using State pattern to implement polymorphic behavior is clearly visible. The chances of error are less and it’s very easy to add more states for additional behavior. Thus making our code more robust, easily maintainable and flexible. Also State pattern helped in avoiding if-else or switch-case conditional logic in this scenario.

image.png

Example of State Design Pattern

In below example, we have implemented a mobile state scenario. With respect to alerts, a mobile can be in different states. For example, vibration and silent. Based on this alert state, behavior of the mobile changes when an alert is to be done.

Strategy pattern(Behavioral)

In Strategy pattern, a class behavior or its algorithm can be changed at run time. In Strategy pattern, we create objects which represent various strategies and a context object whose behavior varies as per its strategy object. The strategy object changes the executing algorithm of the context object.

Template pattern

In Template pattern, an abstract class exposes defined way(s)/template(s) to execute its methods. Its subclasses can override the method implementation as per need but the invocation is to be in the same way as defined by an abstract class. This pattern comes under behavior pattern category.

Promote this post

Share & amplify

LinkedIn post generator

Generate a polished, professional LinkedIn post from this article. Edit before posting.

Comments (0)

Sort:
Sign in to join the discussion.