Design Patterns
Design Patterns
The Singleton Design Pattern ensures that a class has only one instance and provides a global access point to it. It is used
when we want centralized control of resources, such as managing database connections, configuration settings or
logging.
1.Single Instance: Ensures only one object of the class exists in the JVM.
2.Global Access Point: Provides a centralized way to access the instance.
3.Lazy or Eager Initialization: An Instance can be created at class load time (eager) or when first needed (lazy).
4.Thread Safety: Can be designed to work correctly in multithreaded environments.
5.Resource Management: Useful for managing shared resources like configurations, logging or database connections.
6.Flexibility in Implementation: Can be implemented using eager initialization, lazy initialization, double-checked locking or
an inner static class.
Key Components
1. Static Member
The Singleton pattern or pattern Singleton employs a static member within the class. This static member ensures that
memory is allocated only once, preserving the single instance of the Singleton class.
The Singleton pattern or pattern singleton incorporates a private constructor, which serves as a barricade against external
attempts to create instances of the Singleton class. This ensures that the class has control over its instantiation process.
A crucial aspect of the Singleton pattern is the presence of a static factory method. This method acts as a gateway,
providing a global point of access to the Singleton object. When someone requests an instance, this method either creates a
new instance (if none exists) or returns the existing instance to the caller.
In this method, class is initialized whether it is to be used or not. The main advantage of this method is its simplicity. You
initiate the class at the time of class loading. Its drawback is that class is always initialized whether it is being used or not.
Example: Classical Java implementation of singleton design pattern
class Singleton {
private static Singleton obj;
Note: Singleton obj is not created until we need it and call the getInstance() method. This is called lazy instantiation. The main problem
with the above method is that it is not thread-safe. Consider the following execution sequence.
This execution sequence creates two objects for the singleton. Therefore this classic implementation is not thread-safe.
2. Thread-Safe (Synchronized)
class Singleton {
private static Singleton obj;
private Singleton() {}
In this method, class in initialized only when it is required. It can save you from instantiating the class when you don't need
it. Generally, lazy initialization is used when we create a singleton class.
Example: Static initializer based Java implementation of singleton design pattern
class Singleton {
private static Singleton obj = new Singleton();
private Singleton() {}
class Singleton {
private static volatile Singleton obj = null;
private Singleton() {}
private Singleton() {
System.out.println("Instance created");
}
5.Cache Managers, Print Spoolers (Single Printer Queue) and Runtime Environments ( java.lang.Runtime is a
singleton)
Factory method Design Pattern
The Factory Method is a creational design pattern that defines an interface for creating objects but lets subclasses
decide which object to instantiate. It promotes loose coupling by delegating object creation to a method, making the
system more flexible and extensible.
•Subclasses override the factory method to produce specific object types.
•Supports easy addition of new product types without modifying existing code.
•Define a Factory Interface: Create an interface, VehicleFactory, with a method to produce vehicles.
•Create Specific Factories: Implement classes like TwoWheelerFactory and FourWheelerFactory that follow the
VehicleFactory interface, providing methods for each vehicle type.
•Revise the Client Class: Change the Client class to use a VehicleFactory instance instead of creating vehicles directly. This
way, it can request vehicles without using conditional logic.
•Enhance Flexibility: This structure allows for easy addition of new vehicle types by simply creating new factory classes,
without needing to alter existing Client code.
2. With Factory Method Design Pattern
•Concrete Factories: Concrete Factories implement the rules specified by the abstract factory. It contain the logic for
creating specific instances of objects within a family.
•Abstract Products: It acts as an abstract or interface type that all concrete products within a family must follow to and
provides a unified way for concrete products to be used interchangeably.
•Concrete Products: They implement the methods declared in the abstract products, ensuring consistency within a family
and belong to a specific category or family of related objects.
•Client: Client utilizes the abstract factory to create families of objects without specifying their concrete types and
interacts with objects through abstract interfaces provided by abstract products.
Example of Abstract Factory Design Pattern
Imagine you're managing a global car manufacturing company
• You want to design a system to create cars with specific configurations for different regions, such as North America and
Europe.
• Each region may have unique requirements and regulations, and you want to ensure that cars produced for each region
meet those standards.
• Different regions have different cars with different features, so designing this can be challenging.
• The other main challenge is to ensure consistency in the production of cars and their specifications within each region.
• There can be updation in having new cars in different regions so adapting the system to changes in regulations or
introducing new features for a specific region becomes challenging.
• So, Modifications would need to be made in multiple places, increasing the chances of introducing bugs and making the
system more prone to errors.
How Abstracy Factory Pattern help to solve above challenges?
• Different regions has their own factory to create cars for local needs.
• This helps to keeps the design and features the same for vehicles in each region.
• You can change one region without affecting others (e.g., updating North America doesn’t impact Europe).
• To add a new region, just create a new factory, no need to change existing code.
• The pattern keeps car creation separate from how they are used.
1. Abstract Factory Interface /* Abstract Factory Interface */
(CarFactory) interface CarFactory {
Car createCar();
"CarFactory" is a Abstract Factory CarSpecification createSpecification();
Interface that defines methods for creating }
cars and their specifications.
// Concrete Factory for North America Cars
class NorthAmericaCarFactory implements CarFactory {
public Car createCar() {
return new Sedan();
2. Concrete Factories }
(NorthAmericaCarFactory and
EuropeCarFactory) public CarSpecification createSpecification() {
return new NorthAmericaSpecification();
"NorthAmericaCarFactory" and }
"EuropeCarFactory" are concrete factories }
that implement the abstract factory interface
"CarFactory" to create cars and specifications // Concrete Factory for Europe Cars
class EuropeCarFactory implements CarFactory {
specific to North America, Europe.
public Car createCar() {
return new Hatchback();
}
europeCar.assemble();
europeSpec.display();
}
}
When to use Abstract Factory Pattern
Choose using abstract factory pattern when:
• When your system requires multiple families of related products and you want to ensure compatibility between them.
• When you need flexibility and extensibility, allowing for new product variants to be added without changing existing client
code.
• When you want to encapsulate the creation logic, making it easier to modify or extend the object creation process without
affecting the client.
• When you aim to maintain consistency across different product families, ensuring a uniform interface for the products.
Problem Statement:
You need to implement a system for building custom computers with different configurations of CPU, RAM and storage. The
goal is to allow flexibility in creating multiple variations of computers through a step-by-step construction process.
1. Product (Computer) 2. Builder
/* Builder interface */
/* Product */ interface Builder {
class Computer { void buildCPU();
void buildRAM();
private String cpu_; void buildStorage();
private String ram_;
Computer getResult();
private String storage_;
}
public void setCPU(String cpu) {
cpu_ = cpu;
3. ConcreteBuilder (GamingComputerBuilder)
}
class ComputerDirector {
public void construct(GamingComputerBuilder builder) {
builder.construct();
}
}
class Computer {
public void displayInfo() {
System.out.println("Displaying computer info");
}
}
Complete Combined code for the above example // Director
class ComputerDirector {
import java.util.ArrayList; // Builder interface
public void construct(Builder builder) {
interface Builder {
builder.buildCPU();
// Product void buildCPU();
builder.buildRAM();
class Computer { void buildRAM();
builder.buildStorage();
private String cpu; void buildStorage();
}
private String ram; Computer getResult();
}
private String storage; }
// Client
public void setCPU(String cpu) { // ConcreteBuilder
public class Main {
this.cpu = cpu; class GamingComputerBuilder implements Builder {
public static void main(String[] args) {
} private Computer computer = new Computer();
GamingComputerBuilder gamingBuilder = new
GamingComputerBuilder();
public void setRAM(String ram) { public void buildCPU() {
ComputerDirector director = new
this.ram = ram; computer.setCPU("Gaming CPU");
ComputerDirector();
} }
director.construct(gamingBuilder);
public void setStorage(String storage) { public void buildRAM() {
Computer gamingComputer =
this.storage = storage; computer.setRAM("16GB DDR4");
gamingBuilder.getResult();
} }
gamingComputer.displayInfo();
public void displayInfo() { public void buildStorage() {
} Output
System.out.println("Computer Configuration:\n" + computer.setStorage("1TB SSD");
} Computer Configuration:
"CPU: " + cpu + "\n" + }
"RAM: " + ram + "\n" +
public Computer getResult() { CPU: Gaming CPU
"Storage: " + storage + "\n");
} return computer; RAM: 16GB DDR4
} }
} Storage: 1TB SSD
When to Use the Builder Pattern
• When constructing an object is complex and involves many optional steps.
• When you want to separate construction logic from representation.
• When you want to avoid telescoping constructors (constructors with too many parameters).
• When different representations (e.g., gaming computer, office computer) can be built using the same construction process.
• A two-way adapter can function as both a target and an adaptee, depending on which interface is being invoked.
• This type of adapter is particularly useful when two systems need to work together and require mutual adaptation.
• When only a few methods from an interface are necessary, an interface adapter can be employed.
• This is especially useful in cases where the interface contains many methods, and the adapter provides default
implementations for those that are not needed.
• This approach is often seen in languages like Java, where abstract classes or default method implementations in interfaces
simplify the implementation process.
Problem Statement:
Let's consider a scenario where we have an existing system that uses a LegacyPrinter class with a method named printDocument()
which we want to adapt into a new system that expects a Printer interface with a method named print(). We'll use the Adapter
design pattern to make these two interfaces compatible.
1. Target Interface (Printer) /* Target Interface */
interface Printer {
The interface that the client code expects. void print();
}
/* Adaptee */
@Override
public void print() {
legacyPrinter.printDocument();
}
}
Complete Code for the above example:
4. Client Code
/* Client Code */
interface Printer {
void print();
}
// Adapter Output
class PrinterAdapter implements Printer { Legacy Printer is printing a document.
private LegacyPrinter legacyPrinter = new LegacyPrinter();
@Override
public void print() {
legacyPrinter.printDocument();
}
Pros of Adapter Design Pattern
Below are the pros of Adapter Design Pattern:
•Promotes code reuse without modification.
The diagram shows a Pizza (base component) wrapped with Capsicum and then Cheese Burst (decorators). Each adds its own
cost via getCost(), so the final price = Pizza + Capsicum + Cheese Burst. This illustrates how the Decorator Pattern layers extra
behavior dynamically.
Key Features of the Decorator Pattern
• Dynamic Behavior Addition: Add or remove responsibilities at runtime without modifying the original object.
• Open-Closed Principle (OCP): Extend object behavior without altering existing code.
• Composition over Inheritance: Combine multiple behaviors without creating a rigid class hierarchy.
• Reusable Decorators: Same decorators can be applied to different objects.
• Flexible and Scalable: Easily add new features without touching existing code.
Real-World Examples
1.Coffee Shop Application: Customers can customize their coffee with add-ons like milk, sugar or whipped cream. Each add-on
is a decorator dynamically applied to the coffee object.
2.Video Streaming Platforms: Videos can have subtitles, audio enhancements, multiple resolutions or language options, all
added as decorators.
3.Text Processing Applications: A plain text object can be wrapped with BoldDecorator, ItalicDecorator or UnderlineDecorator
to apply multiple formatting styles.
4.Java I/O Library: Classes like FileInputStream can be wrapped by BufferedInputStream or DataInputStream to add additional
functionality without changing the core class.
1. Component: The Component is the common interface for all objects in the composition. It defines the methods that are
common to both leaf and composite objects.
2. Leaf: The Leaf is the individual object that does not have any children. It implements the component interface and
provides the specific functionality for individual objects.
3. Composite: The Composite is the container object that can hold Leaf objects as well as the other Composite objects. It
implements the Component interface and provides methods for adding, removing and accessing children.
4. Client: The Client is responsible for using the Component interface to work with objects in the composition. It treats
both Leaf and Composite objects uniformly.
1. Component
@Override
Here, File is a leaf object. It implements public void display() {
the FileSystemComponent interface by providing a display System.out.println("File: " + name + " (" + size + " bytes)");
method. It contains data specific to files, such as their name and }
size. }
3. Composite import java.util.ArrayList;
import java.util.List;
In the file system hierarchy example, Composite objects are
directories. These are objects that contain other components, abstract class FileSystemComponent {
including both leaf objects (files) and other composite objects abstract void display();
}
(subdirectories). Here's an implementation of a composite
object, a directory: class Directory extends FileSystemComponent {
private String name;
private List<FileSystemComponent> components = new
ArrayList<>();
@Override
void display() {
In this example, Directory is a composite object. Like
System.out.println("Directory: " + name);
the File class, it also implements for (FileSystemComponent component : components) {
the FileSystemComponent interface by providing a display component.display();
method. Additionally it, contains a vector }
of FileSystemComponent to store child components (files or }
subdirectories). The addComponent method allows adding
components to the directory. public void addComponent(FileSystemComponent component) {
components.add(component);
}
4. Client
The Client code interacts with the components through the Component interface, and it doesn't need to be aware of whether it's
working with a leaf or a composite object.
import java.util.ArrayList; class Directory implements FileSystemComponent {
public class Main {
private String name;
public static void main(String[] args) {
interface FileSystemComponent { private ArrayList<FileSystemComponent> components;
// Create leaf objects (files)
void display();
FileSystemComponent file1 = new
} public Directory(String name) {
File("document.txt", 1024);
this.name = name;
FileSystemComponent file2 = new
class File implements FileSystemComponent { this.components = new ArrayList<>();
File("image.jpg", 2048);
private String name; }
private int size;
// Create a composite object (directory)
public void addComponent(FileSystemComponent component)
Directory directory = new Directory("My
public File(String name, int size) { {
Documents");
this.name = name; components.add(component);
this.size = size; }
// Add leaf objects to the directory
}
directory.addComponent(file1);
@Override
directory.addComponent(file2);
@Override public void display() {
public void display() { System.out.println("Directory: " + name);
// Display the directory (including its
System.out.println("File: " + name + " Size: " + for (FileSystemComponent component : components) {
contents)
size); component.display();
directory.display();
} }
}
} Output }
}
Directory:} My Documents
File: document.txt (1024 bytes)
File: image.jpg (2048 bytes)
Diagrammatic representation of the Composite Design Pattern
Advantages of the Composite Design Pattern
•Hierarchical Structure: Represents objects in tree-like hierarchies, treating individuals and composites uniformly.
•Simplified Client Code: Clients interact with objects without distinguishing between single or composite ones.
•Code Reusability: Same operations apply to both parts and wholes, reducing duplication.
•Limited Type Safety: Common interface may allow invalid operations, risking runtime errors.
A real-world example can be a cheque or credit card as a proxy for what is in our bank account. It can be used in place of
cash and provides a means of accessing that cash when required.
Chaining of Proxies
Chaining proxies in the Proxy Design Pattern means connecting them in a sequence, where each proxy adds its behavior or
checks before passing the request to the next proxy or the real object. It's like forming a chain of guards, each responsible for a
specific task.
Components of Proxy Design Pattern
1. Subject
The Subject is an interface or an abstract class that defines the common interface shared by the RealSubject and Proxy classes. It
declares the methods that the Proxy uses to control access to the RealSubject .
• Declares the common interface for both RealSubject and Proxy .
• Usually includes the methods that the client code can invoke on the RealSubject and the Proxy .
2. RealSubject
The RealSubject is the actual object that the Proxy represents. It contains the real implementation of the business logic or the
resource that the client code wants to access.
• It Implements the operations declared by the Subject interface.
• Represents the real resource or object that the Proxy controls access to.
3. Proxy
The Proxy acts as a surrogate or placeholder for the RealSubject . It controls access to the real object and may provide additional
functionality such as lazy loading, access control, or logging.
• Implements the same interface as the RealSubject (Subject).
• Maintains a reference to the RealSubject .
• Controls access to the RealSubject , adding additional logic if necessary.
How to implement Proxy Design Pattern?
1. Create the Real Object Interface: Define an interface or abstract class that represents the operations the real object will
provide. Both the real object and proxy will implement this interface.
2. Create the Real Object: This class implements the interface and contains the actual logic or operation that the client wants
to use.
3. Create the Proxy Class: The proxy class also implements the same interface as the real object. It holds a reference to the
real object and controls access to it. The proxy can add extra logic like logging, caching, or security checks before calling
the real object's methods.
4. Client Uses the Proxy: Instead of creating the real object directly, the client interacts with the proxy. The proxy decides
when and how to forward the client’s request to the real object.
Proxy Design Pattern example (with implementation)
Consider a scenario where your application needs to load and
display images, and you want to optimize the image loading
process. Loading images from disk or other external sources
can be resource-intensive, especially if the images are large or
stored remotely.
To address this issue, we need to implement the Proxy Design
Pattern to control the access and loading of images.
1. Subject (Image Interface):
The Image interface declares the common methods for displaying // Subject
images, acting as a blueprint for both the real and proxy objects. In interface Image {
this design, it defines the display() method that void display();
both RealImage and ProxyImage must implement. This ensures a }
uniform interface for clients interacting with image objects.
// RealSubject
class RealImage implements Image {
private String filename;
2. RealSubject (RealImage Class):
public RealImage(String filename) {
The RealImage class represents the real object that the proxy will this.filename = filename;
control access to. loadImageFromDisk();
•It implements the Image interface, providing concrete }
implementations for loading and displaying images from disk.
private void loadImageFromDisk() {
•The constructor initializes the image file name, and System.out.println("Loading image: " + filename);
the display() method is responsible for loading the image if not }
already loaded and then displaying it.
public void display() {
System.out.println("Displaying image: " + filename);
}
}
abstract class Image {
3. Proxy (ProxyImage Class): public abstract void display();
}
The ProxyImage class acts as a surrogate for the RealImage . It also
class RealImage extends Image {
implements the Image interface, maintaining a reference to the private String filename;
real image object.
public RealImage(String filename) {
•The display() method in the proxy checks whether the real image this.filename = filename;
has been loaded; if not, it creates a new instance of RealImage and System.out.println("Loading " + filename);
delegates the display() call to it. }
@Override
public void display() {
•This lazy loading mechanism ensures that the real image is System.out.println("Displaying " + filename);
loaded only when necessary. }
}
class ProxyImage extends Image {
private String filename;
private RealImage realImage = null;
The client code (ProxyPatternExample ) demonstrates the usage of the Proxy Design Pattern. It creates an Image object, which is
actually an instance of ProxyImage .
• The client invokes the display() method on the proxy.
• The proxy, in turn, controls access to the real image, ensuring that it is loaded from disk only when needed.
• Subsequent calls to display() use the cached image in the proxy, avoiding redundant loading and improving performance.
class ProxyImage {
import java.util.*; private String filename; // Client code
private Image image; public class Main {
class Image { public static void main(String[] args) {
private String filename; public ProxyImage(String filename) { ProxyImage image = new
this.filename = filename; ProxyImage("example.jpg");
public Image(String filename) { this.image = null;
this.filename = filename; } // Image will be loaded from disk only when
_loadFromDisk(); display() is called
} public void display() { image.display();
if (image == null) {
private void _loadFromDisk() { image = new Image(filename); // Image will not be loaded again, as it has been
System.out.println("Loading " + filename + " from disk"); } cached in the Proxy
} image.display(); image.display();
} }
public void display() { } }
System.out.println("Displaying image: " + filename);
}
}
Output:
Loading image: example.jpg
Displaying image: example.jpg
Displaying image: example.jpg
• First call to display() → Proxy creates RealImage , loads it, then displays it.
Note: Subjects are the objects that maintain and notify observers about changes in their state, while Observers are the
entities that react to those changes.
Key Features of the Observer Design Pattern
1. Loose Coupling: Subjects don’t need to know details about observers.
2. Dynamic Relationships: Observers can be added or removed at runtime.
3. Scalability: Works well when multiple objects depend on the same subject.
4. Reusability: Observers and subjects can be reused independently.
5. Automatic Synchronization: Any state change in the subject is propagated to all observers.
6. Flexibility: Supports many-to-many relationships (multiple subjects and multiple observers).
Real-world analogy of the Observer Design Pattern
Imagine a Weather Station being monitored by different smart devices:
•The Weather Station (Subject) maintains weather data.
•Devices (Observers) like mobile apps, TVs, and smartwatches display the latest weather.
•Whenever the weather changes, all registered devices are automatically notified and updated.
This allows new devices to join or leave without affecting the weather station’s code.
Components of Observer Design Pattern
Challenges or difficulties while implementing this system without Observer Design Pattern
•Components interested in weather updates would need direct references to the weather monitoring system, leading
to tight coupling.
•Adding or removing components that react to weather changes requires modifying the core weather monitoring system
code, making it hard to maintain.
The Observer Pattern facilitates the decoupling of the weather monitoring system from the components that are
interested in weather updates (via interfaces). Every element can sign up as an observer, and observers are informed
when the weather conditions change. The weather monitoring system is thus unaffected by the addition or removal of
components.
1. Subject
2. Observer
•"notifyObservers" iterates through the observers and calls their // Concrete Subject
class WeatherStation implements Subject {
"update" method, passing the current weather. private List<Observer> observers = new ArrayList<>();
private String weather = "";
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(weather);
}
}
•"PhoneDisplay" is a concrete observer implementing the public class PhoneDisplay implements Observer {
"Observer" interface. private String weather;
•It has a private field weather to store the latest weather. @Override
public void update(Observable o, Object arg) {
•The "update" method sets the new weather and calls the if (arg instanceof String) {
"display" method. this.weather = (String) arg;
display();
•"display" prints the updated weather to the console. }
}
}
}
Output
Phone Display: Weather updated - Sunny
TV Display: Weather updated - Sunny
Phone Display: Weather updated - Rainy
TV Display: Weather updated - Rainy
Phone Display: Weather updated - Cloudy
TV Display...
Strategy Design Pattern
Strategy Design Pattern is a behavioral design pattern that allows you to define a family of algorithms or behaviors, put each
of them in a separate class, and make them interchangeable at runtime. This pattern is useful when you want to dynamically
change the behavior of a class without modifying its code.
• File compression utilities can employ the Strategy pattern to offer different compression methods (e.g., ZIP, GZIP,
TAR) to the user, allowing them to choose the desired compression strategy.
Components of the Strategy Design Pattern
1. Context
A class or object known as the Context assigns the task to a strategy object and contains a reference to it.
• It serves as an intermediary between the client and the strategy, offering an integrated approach for task execution without exposing
every detail of the process.
• The Context maintains a reference to a strategy object and calls its methods to perform the task, allowing for interchangeable
strategies to be used.
2. Strategy Interface
An abstract class or interface known as the Strategy Interface specifies a set of methods that all concrete strategies must implement.
• As a kind of agreement, it guarantees that all strategies follow the same set of rules and are interchangeable by the Context.
• The Strategy Interface promotes flexibility and modularity in the design by establishing a common interface that enables
decoupling between the Context and the specific strategies.
3. Concrete Strategies
Concrete Strategies are the various implementations of the Strategy Interface. Each concrete strategy provides a specific algorithm or
behavior for performing the task defined by the Strategy Interface.
• Concrete strategies encapsulate the details of their respective algorithms and provide a method for executing the task.
• They are interchangeable and can be selected and configured by the client based on the requirements of the task.
4. Client
The Client is responsible for selecting and configuring the appropriate strategy and providing it to the Context.
• It knows the requirements of the task and decides which strategy to use based on those requirements.
• The client creates an instance of the desired concrete strategy and passes it to the Context, enabling the Context to use the selected
strategy to perform the task.
Communication between the Components
In the Strategy Design Pattern, communication between the components occurs in a structured and decoupled manner.
Here's how the components interact with each other:
Client -> Context: The client selects and configures a suitable strategy, then passes it to the context to execute the task.
Context -> Strategy: The context holds the strategy reference and delegates execution to it via the common interface.
Strategy -> Context: The strategy executes its algorithm, returns results, or performs necessary actions for the context to
use.
Strategy Interface: Defines a contract ensuring all strategies are interchangeable.
Decoupling: Context remains unaware of strategy details, enabling flexibility and easy substitution.
// MergeSortStrategy
public class MergeSortStrategy implements SortingStrategy {
@Override
public void sort(int[] array) {
// Implement Merge Sort algorithm
System.out.println("Sorting using Merge Sort");
}
}
// QuickSortStrategy
public class QuickSortStrategy implements SortingStrategy {
@Override
public void sort(int[] array) {
// Implement Quick Sort algorithm
System.out.println("Sorting using Quick Sort");
}
}
4. Client Component
public interface SortingStrategy {
void sort(String[] array);
}
class SortingContext {
private SortingStrategy strategy;
For example, a vending machine responds differently based on state (item selected, money paid and item dispensed).
This pattern focuses on managing state transitions and coordinating state-specific behaviors.
Real Life Software Examples
• Media Player behaves differently in each play, pause and stop states. The internal state determines what action happens
when a button is pressed. For example, in play state, it responds to pause and stop.
• Document (Google Docs and MS Word) respond differently according to the current status of the document. In Draft:
only the author can edit. In Review: reviewers can comment, but not edit and in Published: it becomes read-only.
• State Interface/Base Class – Defines common methods for all states, allowing Context to work with them without
knowing concrete types.
• Concrete States – Implement the State interface, encapsulating behavior for specific states and defining Context’s
actions in those states.
How to implement State Design Pattern?
Below are the steps to implement the State Design Pattern:
• Purpose – Lets an object change behavior based on its state, avoiding complex conditionals by using separate state
classes.
• Context – The object whose behavior varies (e.g., media player: Playing, Paused, Stopped).
• State Interface – Defines common actions all states must implement.
• Concrete States – Implement the interface, each defining behavior for a specific state.
• Context Class – Holds the current state and delegates actions to it, enabling dynamic state switching at runtime.
Communication between the components
In the State design pattern, the communication between the components typically follows these steps:
• Client Interaction – Client calls methods on the Context.
• Delegation – Context forwards the request to its current State.
• State Behavior – The State executes behavior for that state.
• Transition Check – Logic may trigger a state change.
• State Update – Context updates its reference to the new State.
• Repeat – Client keeps interacting, and behavior adapts per state
This communication flow ensures that the Context and State objects work together seamlessly to achieve dynamic behavior
changes based on the internal state of the Context. The Context manages the state and delegates behavior to the current State
object, while the State objects encapsulate state-specific behavior and handle transitions between states as necessary.
Example of State Design Pattern
Problem Statement:
Imagine a vending machine that sells various products. The vending machine needs to manage different states such as ready to
serve, waiting for product selection, processing payment, and handling out-of-stock situations. Design a system that models the
behavior of this vending machine efficiently.
How State Design Pattern will help while building this system:
• Modeling States – Each state (Ready, Product Selected, Payment Pending, Out of Stock) is represented as a separate class,
keeping the code organized.
• Encapsulation – Each state class defines its own behavior (e.g., ReadyState handles selection, PaymentPendingState
handles payments), simplifying complex logic.
• Dynamic Transitions – The machine moves between states (e.g., Ready → Product Selected → Payment Pending) based on
user actions.
• Reusability – State classes can be reused across different vending machine implementations, reducing duplication.
• Maintainability & Flexibility – New states or changes can be added without impacting other parts, making the system
easier to extend and maintain.
This interface defines the contract that all concrete state classes /* Public interface VendingMachineState */
must implement. It typically contains a method or methods public interface VendingMachineState {
representing the behavior associated with each state of the void handleRequest();
vending machine. }
import java.util.ArrayList;
// VendingMachineState interface
3. Concrete States (Specific Vending Machine States) interface VendingMachineState {
void handleRequest();
}
// ReadyState class
Concrete state classes represent specific states of the vending class ReadyState implements VendingMachineState {
@Override
machine, such as "ReadyState," "ProductSelectedState," and public void handleRequest() {
System.out.println("Ready state: Please select a product.");
}
"OutOfStockState." Each concrete state class implements the }
behavior associated with its respective state, like allowing // ProductSelectedState class
class ProductSelectedState implements VendingMachineState {
@Override
product selection, processing payment, or displaying an public void handleRequest() {
System.out.println("Product selected state: Processing payment.");
}
out-of-stock message. }
// PaymentPendingState class
class PaymentPendingState implements VendingMachineState {
@Override
public void handleRequest() {
System.out.println("Payment pending state: Dispensing product.");
}
}
// OutOfStockState class
class OutOfStockState implements VendingMachineState {
@Override
public void handleRequest() {
System.out.println("Out of stock state: Product unavailable. Please select another product.");
}
}
// VendingMachine class
class VendingMachine {
private VendingMachineState currentState;
public VendingMachine() {
this.currentState = new ReadyState();
}
// Main class
public class Main {
public static void main(String[] args) {
VendingMachine vendingMachine = new VendingMachine();
vendingMachine.handleRequest();
vendingMachine.setState(new ProductSelectedState());
vendingMachine.handleRequest();
Output:
Ready state: Please select a product.
Product selected state: Processing payment.
Payment pending state: Dispensing product.
Out of stock state: Product unavailable. Please select another
product.
•When a user interacts with the vending machine (Context), such as inserting money or selecting a product, the vending
machine delegates the responsibility of handling the interaction to the current state object.
•The current state object (e.g., "ReadyState" or "ProductSelectedState") executes the behavior associated with that state,
such as processing the payment or dispensing the selected product.
•Depending on the outcome of the interaction and the logic implemented within the current state object, the vending
machine may transition to a different state.
•The process continues as the user interacts further with the vending machine, with behavior delegated to the
appropriate state object based on the current state of the vending machine.
Chain of Responsibility Design Pattern
The Chain of Responsibility design pattern is a behavioral design pattern that allows an object to pass a request along a
chain of handlers. Each handler in the chain decides either to process the request or to pass it along the chain to the next
handler.
•This pattern encourages loose coupling between sender and receiver, providing freedom in handling the request.
Characteristics of the Chain of Responsibility Design Pattern
• Loose Coupling: This means the sender of a request doesn't need to know which specific object will handle it. Similarly,
the handler doesn’t need to understand how the requests are sent. This keeps the components separate and flexible.
• Dynamic Chain: While the program is running, changing the chain is simple. This makes your code incredibly flexible
because you may add or delete handlers without changing the main body of the code.
• Single Responsibility Principle: Each handler in the chain has one job: either to handle the request or to pass it to the
next handler. This keeps the code organized and focused, making it easier to manage.
• Sequential Order: Requests move through the chain one at a time. Each handler gets a chance to process the request in a
specific order, ensuring consistency.
• Fallback Mechanism: If a request isn’t handled by any of the handlers, the chain can include a fallback option. This
means there's a default way to deal with requests that don't fit anywhere else.
Real-World Analogy of the Chain Of Responsibility Design Pattern
Imagine a customer service department with multiple levels of support staff, each responsible for handling different types of
customer inquiries based on their complexity. The chain of responsibility can be illustrated as follows:
• Level 1 Support: This represents the first point of contact for customer inquiries. Level 1 support staff handle basic
inquiries and provide general assistance. If they cannot resolve the issue, they escalate it to Level 2 support.
• Level 2 Support: This level consists of more experienced support staff who can handle more complex issues that Level 1
support cannot resolve. If Level 2 support cannot resolve the issue, they escalate it to Level 3 support.
• Level 3 Support: This is the highest level of support, consisting of senior or specialized staff who can handle critical or
highly technical issues. If Level 3 support cannot resolve the issue, they may involve other departments or experts within the
organization.
Components of the Chain of Responsibility Design Pattern
•Handler Interface or Abstract Class: This is the base class that defines the interface for handling requests and, in many
cases, for chaining to the next handler in the sequence.
•Concrete Handlers: These are the classes that implement how the requests are going to be handled. They can handle the
request or pass it to the next handler in the chain if it is unable to handle that request.
•Client: The request is sent by the client, who then forwards it to the chain’s first handler. Which handler will finally handle the
request is unknown to the client.
•Step 2: Create Concrete Handlers: Implement the handler interface in multiple classes, each handling specific requests and
passing unhandled requests to the next handler.
•Step 3: Set Up the Chain: Create instances of your handlers and link them together by setting the next handler for each one.
•Step 4: Send Requests: Use the first handler in the chain to send requests, allowing each handler to decide whether to process
it or pass it along.
Chain of Responsibility Design Pattern Example
Imagine a customer support system where customer requests need to be handled based on their priority. There are three
levels of support: Level 1, Level 2, and Level 3. Level 1 support handles basic requests, Level 2 support handles more
complex requests, and Level 3 support handles critical issues that cannot be resolved by Level 1 or Level 2.
1. Handler Interface
public interface SupportHandler {
Defines the interface for handling requests. Includes methods void handleRequest(Request request);
for handling requests (handleRequest()) and setting the next void setNextHandler(SupportHandler nextHandler);
handler in the chain (setNextHandler()). }
2. Concrete Handlers
• In logging systems, you might have different levels of loggers (like INFO, WARN, ERROR). Each logger can
handle specific log messages. If one logger can’t process a message (for example, if it's below its level), it passes it
to the next logger in the chain.
• In security systems, access requests can be processed by a series of handlers that check permissions. For instance,
one handler might check user roles, while another checks specific permissions. If one handler denies access, it can
pass the request to the next handler for further evaluation.
Pros of the Chain of Responsibility Design Pattern
Below are the pros of chain of responsibility design pattern:
• The pattern makes enables sending a request to a series of possible recipients without having to worry about which object
will handle it in the end. This lessens the reliance between items.
• New handlers can be easily added or existing ones can be modified without affecting the client code. This promotes
flexibility and extensibility within the system.
• The sequence and order of handling requests can be changed dynamically during runtime, which allows adjustment of the
processing logic as per the requirements.
• It simplifies the interaction between the sender and receiver objects, as the sender does not need to know about the
processing logic.
Cons of the Chain of Responsibility Design Pattern
Below are the cons of chain of responsibility design pattern:
• The chain should be implemented correctly otherwise there is a chance that some requests might not get handled at all,
which leads to unexpected behavior in the application.
• The request will go through several handlers in the chain if it is lengthy and complicated, which could cause performance
overhead. The processing logic of each handler has an effect on the system’s overall performance.
• The fact that the chain has several handlers can make debugging more difficult. Tracking the progression of a request and
determining which handler is in charge of handling it can be difficult.
• It may become more difficult to manage and maintain the chain of responsibility if the chain is dynamically modified at
runtime.