0% found this document useful (0 votes)
14 views101 pages

Design Patterns

The Singleton Design Pattern ensures a class has only one instance with a global access point, commonly used for resource management like database connections. Key features include lazy/eager initialization, thread safety, and flexibility in implementation. The document also discusses various implementation methods such as classic lazy initialization, thread-safe methods, eager initialization, double-checked locking, and using static inner classes.

Uploaded by

Bindushree Bade
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
14 views101 pages

Design Patterns

The Singleton Design Pattern ensures a class has only one instance with a global access point, commonly used for resource management like database connections. Key features include lazy/eager initialization, thread safety, and flexibility in implementation. The document also discusses various implementation methods such as classic lazy initialization, thread-safe methods, eager initialization, double-checked locking, and using static inner classes.

Uploaded by

Bindushree Bade
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Singleton Method Design Pattern

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.

Features of the Singleton Design Pattern

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.

// Static member to hold the single instance


private static Singleton instance;
2. Private Constructor

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.

// Private constructor to prevent external instantiation


class Singleton {

// Making the constructor as Private


private Singleton()
{
// Initialization code here
}
}
3. Static Factory Method

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.

// Static factory method for global access


public static Singleton getInstance()
{
// Check if an instance exists
if (instance == null) {
// If no instance exists, create one
instance = new Singleton();
}
// Return the existing instance
return instance;
}
Different Ways to Implement Singleton Method Design Pattern
Sometimes we need to have only one instance of our class for example a single DB connection shared by multiple objects as
creating a separate DB connection for every object may be costly. Similarly, there can be a single configuration manager or
error manager in an application that handles all problems instead of creating multiple managers.
1. Classic (Lazy Initialization)

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;

// private constructor to force use of getInstance() to create Singleton object


private Singleton() {}

public static Singleton getInstance()


{
if (obj == null)
obj = new Singleton();
return obj;
}
}
Here we have declared getInstance() static so that we can call it without instantiating the class. The first time getInstance() is called it
creates a new singleton object and after that, it just returns the same object.

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)

Make getInstance() synchronized to implement Singleton Method Design Pattern


Example: Thread Synchronized Java implementation of singleton design pattern

class Singleton {
private static Singleton obj;
private Singleton() {}

// Only one thread can execute this at a time


public static synchronized Singleton getInstance()
{
if (obj == null)
obj = new Singleton();
return obj;
}
}
Here using synchronized makes sure that only one thread at a time can execute getInstance(). The main disadvantage of this
method is that using synchronized every time while creating the singleton object is expensive and may decrease the
performance of your program. However, if the performance of getInstance() is not critical for your application this method
provides a clean and simple solution.
3. Eager Initialization (Static Block)

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() {}

public static Singleton getInstance() { return obj; }


}
Here we have created an instance of a singleton in a static initializer. JVM executes a static initializer when the class is
loaded and hence this is guaranteed to be thread-safe. Use this method only when your singleton class is light and is used
throughout the execution of your program.
4. Double-Checked Locking (Most Efficient)

Use “Double Checked Locking” to implement singleton design pattern


Example: Double Checked Locking based Java implementation of singleton design pattern

class Singleton {
private static volatile Singleton obj = null;
private Singleton() {}

public static Singleton getInstance()


{
if (obj == null) {
// To make thread safe
synchronized (Singleton.class)
{
// check again as multiple threads can reach above step
if (obj == null)
obj = new Singleton(); We have declared the obj volatile which ensures that multiple
} threads offer the obj variable correctly when it is being
} initialized to the Singleton instance. This method drastically
return obj; reduces the overhead of calling the synchronized method every
} time.
}
5. Static Inner Class (Best Java-Specific Way)

In Java, a Singleton can be implemented using a static inner class.


•A class is loaded into memory only once by the JVM.
•An inner class is loaded only when it is referenced.
•Therefore, the Singleton instance is created lazily, only when the getInstance() method accesses the inner class.
Example: using class loading concep singleton design pattern

public class Singleton {

private Singleton() {
System.out.println("Instance created");
}

private static class SingletonInner{


In the above code, we are having a private static inner class
SingletonInner and having private field. Through, getInstance()
private static final Singleton INSTANCE=new Singleton();
method of singleton class, we will access the field of inner class
}
and due to being inner class, it will be loaded only one time at
public static Singleton getInstance()
the time of accessing the INSTANCE field first time. And the
{
INSTANCE is a static member due to which it will be
return SingletonInner.INSTANCE;
initialized only once.
}
}
Implementation of the singleton Design pattern
Example: The implementation of the singleton Design pattern is very simple and consists of a single class.
import java.io.*; class GFG {
class Singleton { public static void main(String[] args)
// static class {
private static Singleton instance; Singleton.getInstance().doSomething();
private Singleton() }
{ }
System.out.println("Singleton is Instantiated.");
}
public static Singleton getInstance()
{
if (instance == null) Output
instance = new Singleton(); Singleton is Instantiated.
return instance; Something is Done.
}
public static void doSomething()
{
System.out.println("Something is Done.");
}
}
Real-World Applications of the Singleton Pattern

1.Logging Systems : Maintain a consistent logging mechanism across an application.

2.Configuration Managers : Centralize access to configuration settings.

3.Database Connections : Manage a single point of database access.

4.Thread Pools : Efficiently manage a pool of threads for concurrent tasks.

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.

•Enhances maintainability and adaptability at runtime.


Features of Factory Method Design Pattern
• Encapsulation of Object Creation: Clients don’t know how objects are created.
• Loose Coupling: Reduces dependency between client and concrete classes.
• Scalability: New product types can be introduced without altering client code.
• Reusability: Common creation logic can be reused across factories.
• Flexibility: Supports multiple product families with minimal changes.
• Testability: Easy to use mock factories for unit testing

Components of Factory Method Design Pattern


Below are the main components of Factory Design Pattern:
• Product: Abstract interface or class for objects created by the factory.
• Concrete Product: The actual object that implements the product interface.
• Creator (Factory Interface/Abstract Class): Declares the factory method.
• Concrete Creator (Concrete Factory): Implements the factory method to create specific products.
Factory Method Design Pattern Example
Consider a software application that needs to handle the creation of various types of vehicles, such as Two Wheelers, Three
Wheelers and Four Wheelers. Each type of vehicle has its own specific properties and behaviors.
1. Without Factory Method Design Pattern // Client (or user) class
class Client { // Driver program
import java.io.*; private Vehicle pVehicle; public class GFG {
public static void main(String[] args) {
// Library classes public Client(int type) { Client pClient = new Client(1);
abstract class Vehicle { if (type == 1) { Vehicle pVehicle = pClient.getVehicle();
pVehicle = new TwoWheeler();
public abstract void printVehicle(); if (pVehicle != null) {
} else if (type == 2) { pVehicle.printVehicle();
} pVehicle = new FourWheeler(); }
} else { pClient.cleanup();
class TwoWheeler extends Vehicle { pVehicle = null; }
public void printVehicle() { } }
System.out.println("I am two wheeler"); }
}
public void cleanup() {
}
if (pVehicle != null) {
pVehicle = null;
Output
class FourWheeler extends Vehicle { } I am two wheeler
public void printVehicle() { }
System.out.println("I am four wheeler");
} public Vehicle getVehicle() {
} return pVehicle;
}
}
Issues with the Current Design

•Tight coupling: Client depends directly on product classes.

•Violation of SRP: Client handles both product creation and usage.

•Hard to extend: Adding a new vehicle requires modifying the client.

Solutions to the Problems

•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

Let's breakdown the code into component wise code:


1. Product Interface public abstract class Vehicle {
// Constructor to prevent direct instantiation
Product interface representing a vehicle private Vehicle() {
throw new UnsupportedOperationException("Cannot construct
Vehicle instances directly");
}

// Abstract method to be implemented by subclasses


public abstract void printVehicle();
}
public class Vehicle {
public void printVehicle() {
// This method should be overridden
}
}
2. Concrete Products
public class TwoWheeler extends Vehicle {
Concrete product classes representing different types of vehicles @Override
public void printVehicle() {
System.out.println("I am two wheeler");
}
}

public class FourWheeler extends Vehicle {


@Override
public void printVehicle() {
System.out.println("I am four wheeler");
}
}
3. Creator Interface (Factory Interface) public interface VehicleFactory {
Vehicle createVehicle();
Factory interface defining the factory method }

public interface Vehicle {}

public class TwoWheeler implements Vehicle {}

4. Concrete Creators (Concrete Factories) public class FourWheeler implements Vehicle {}

Concrete factory class for TwoWheeler public interface VehicleFactory {


Vehicle createVehicle();
}

public class TwoWheelerFactory implements VehicleFactory {


public Vehicle createVehicle() {
return new TwoWheeler();
}
}

public class FourWheelerFactory implements VehicleFactory {


public Vehicle createVehicle() {
return new FourWheeler();
}
}
Complete Code of this example: // Concrete Factory for FourWheeler
class FourWheelerFactory implements VehicleFactory {
// Library classes public Vehicle createVehicle() {
abstract class Vehicle { return new FourWheeler();
public abstract void printVehicle(); }
} }
// Client class Output
class TwoWheeler extends Vehicle { class Client {
private Vehicle pVehicle;
I am two wheeler
public void printVehicle() {
System.out.println("I am two wheeler"); I am four wheeler
} public Client(VehicleFactory factory) {
pVehicle = factory.createVehicle();
}
}
class FourWheeler extends Vehicle { public Vehicle getVehicle() {
public void printVehicle() { return pVehicle; // Driver program
System.out.println("I am four wheeler"); } public class GFG {
} } public static void main(String[] args) {
} VehicleFactory twoWheelerFactory = new TwoWheelerFactory();
Client twoWheelerClient = new Client(twoWheelerFactory);
// Factory Interface Vehicle twoWheeler = twoWheelerClient.getVehicle();
interface VehicleFactory { twoWheeler.printVehicle();
Vehicle createVehicle();
} VehicleFactory fourWheelerFactory = new FourWheelerFactory();
Client fourWheelerClient = new Client(fourWheelerFactory);
// Concrete Factory for TwoWheeler Vehicle fourWheeler = fourWheelerClient.getVehicle();
class TwoWheelerFactory implements VehicleFactory { fourWheeler.printVehicle();
}
public Vehicle createVehicle() {
}
return new TwoWheeler();
}
Abstract Factory Pattern
The Abstract Factory Pattern is a creational design pattern that provides an interface for creating families of related or
dependent objects without specifying their concrete classes. It acts as a "factory of factories," where a super-factory creates
other factories that in turn produce specific objects. This adds a higher level of abstraction, allowing systems to switch
between different product families easily while keeping the code organized and loosely coupled.
• Works around a super-factory that produces multiple factories.
• Concrete factories created at runtime decide the actual product types.
• Useful when the system needs to be independent of how products are created or represented.
• Makes it easy to switch between different groups of related objects without code changes.

Real Life Software Examples


• I applications that need to support multiple cloud platforms (e.g., AWS, Azure, Google Cloud), the Abstract Factory
Pattern facilitates the creation of cloud-specific services. An abstract factory can define interfaces for services like
storage, compute, and networking.
• In applications that need to support multiple database systems (e.g., SQL, NoSQL), the Abstract Factory Pattern can be
used to create database connections and queries. An abstract factory defines interfaces for creating database-related
objects.
Components of Abstract Factory Pattern
To understand abstract factory pattern, we have to understand the components of it and relationships between them.
•Abstract Factory: It provides a way such that concrete factories follow a common interface, providing consistent way to
produce related set of objects.

•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.

What can be the challenges while implementing this system?

• 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();
}

public CarSpecification createSpecification() {


return new EuropeSpecification();
}
}
// Abstract Product Interface for Cars
interface Car {
3. Abstract Products (Car and CarSpecification interfaces) void assemble();
}
Define interfaces for cars and specifications to ensure a common
structure. // Abstract Product Interface for Car Specifications
interface CarSpecification {
void display();
// Concrete Product for Sedan Car }
interface Car {
void assemble();
}

class Sedan implements Car {


public void assemble() {
System.out.println("Assembling Sedan car.");
}
} 4. Concrete Products (Sedan, Hatchback,
// Concrete Product for Hatchback Car
class Hatchback implements Car {
NorthAmericaSpecification, EuropeSpecification)
public void assemble() {
System.out.println("Assembling Hatchback car.");
} "Sedan", "Hatchback", "NorthAmericaSpecification",
}
"EuropeSpecification" are concrete products that implement
// Concrete Product for North America Car Specification
interface CarSpecification { the interfaces to create specific instances of cars and
void display();
} specifications.
class NorthAmericaSpecification implements CarSpecification {
public void display() {
System.out.println("North America Car Specification: Safety features compliant with local regulations.");
}
}

// Concrete Product for Europe Car Specification


class EuropeSpecification implements CarSpecification {
public void display() {
System.out.println("Europe Car Specification: Fuel efficiency and emissions compliant with EU standards.");
}
}
// Abstract Product Interface for Cars
Complete code for the above example interface Car {
// Abstract Factory Interface void assemble();
interface CarFactory { }
Car createCar();
CarSpecification createSpecification(); // Abstract Product Interface for Car Specifications
} interface CarSpecification {
void display();
// Concrete Factory for North America Cars }
class NorthAmericaCarFactory implements CarFactory {
public Car createCar() { // Concrete Product for Sedan Car
return new Sedan(); class Sedan implements Car {
} public void assemble() {
System.out.println("Assembling Sedan car.");
public CarSpecification createSpecification() { }
return new NorthAmericaSpecification(); }
}
} // Concrete Product for Hatchback Car
class Hatchback implements Car {
// Concrete Factory for Europe Cars public void assemble() {
class EuropeCarFactory implements CarFactory { System.out.println("Assembling Hatchback car.");
public Car createCar() { }
return new Hatchback(); }
}
// Concrete Product for North America Car Specification
public CarSpecification createSpecification() { class NorthAmericaSpecification implements CarSpecification {
return new EuropeSpecification(); public void display() {
} System.out.println("North America Car Specification: Safety features
} compliant with local regulations.");
}
}
// Concrete Product for Europe Car Specification
class EuropeSpecification implements CarSpecification {
public void display() {
System.out.println("Europe Car Specification: Fuel efficiency and emissions
compliant with EU standards.");
}
}

// Client Code Output


public class CarFactoryClient { Assembling Sedan car.
public static void main(String[] args) {
// Creating cars for North America North America Car Specification: Safety
CarFactory northAmericaFactory = new NorthAmericaCarFactory(); features compliant with local regulations.
Car northAmericaCar = northAmericaFactory.createCar();
CarSpecification northAmericaSpec = Assembling Hatchback car.
northAmericaFactory.createSpecification();
Europe Car Specification: Fuel efficiency and
northAmericaCar.assemble(); emissions compliant wit...
northAmericaSpec.display();

// Creating cars for Europe


CarFactory europeFactory = new EuropeCarFactory();
Car europeCar = europeFactory.createCar();
CarSpecification europeSpec = europeFactory.createSpecification();

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.

When not to use Abstract Factory Pattern


Aviod using abstract factory pattern when:
• The product families are unlikely to change, as it may add unnecessary complexity.
• When your application only requires single, independent objects and isn't concerned with families of related products.
• When overhead of maintaining multiple factories outweighs the benefits, particularly in smaller applications.
• When simpler solutions, like the Factory Method or Builder pattern, if they meet your needs without adding the complexity
of the Abstract Factory pattern.
Builder Design Pattern
The Builder Design Pattern is a creational design pattern that provides a step-by-step approach to constructing complex
objects. It separates the construction process from the object’s representation, enabling the same method to create different
variations of an object.
•Encapsulates object construction logic in a separate Builder class.
•Allows flexible and controlled object creation.
•Supports different variations of a product using the same process.
•Improves readability and maintainability by avoiding long constructors with many parameters.
Components of the Builder Design Pattern
• Product: The Product is the complex object that the Builder pattern is responsible for constructing.
• Builder: An interface/abstract class that defines steps for building parts of the product.
• ConcreteBuilder: Implements the Builder interface with specific logic to build a variation of the product.
• Director: Manages the building process and defines the order of construction.
• Client: Initiates the building process and requests the final product.
Steps to implement the Builder Design Pattern
1.Create the Product Class: Define the object (product) that will be built. This class contains all the fields that make up the object.
2.Create the Builder Class: This class will have methods to set the different parts of the product. Each method returns the builder itself to allow method chaining.
3.Add a Build Method: In the builder class, add a method called build() (or similar) that assembles the product and returns the final object.
4.Use the Director (Optional): If needed, you can create a director class to control the building process and decide the order in which parts are constructed.
5.Client Uses the Builder: The client will use the builder to set the desired parts step by step and call the build() method to get the final product.
Builder Design Pattern Example

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)
}

public void setRAM(String ram) { public interface Builder {


ram_ = ram; void buildCPU();
}
void buildRAM();
public void setStorage(String storage) { void buildStorage();
storage_ = storage; Computer getResult();
}
}
public void displayInfo() {
System.out.println("Computer Configuration:"
+ "\nCPU: " + cpu_
+ "\nRAM: " + ram_
+ "\nStorage: " + storage_ + "\n\n");
}
}
4. Director 5. Client
/* Client */
public class Main {
/* Director */ public static void main(String[] args) {
public class ComputerDirector { GamingComputerBuilder gamingBuilder = new GamingComputerBuilder();
ComputerDirector director = new ComputerDirector();
public void construct(Builder builder) {
builder.buildCPU(); director.construct(gamingBuilder);
Computer gamingComputer = gamingBuilder.getResult();
builder.buildRAM();
builder.buildStorage(); gamingComputer.displayInfo();
}
} }
} class GamingComputerBuilder {
public void construct() {
// Implementation
}
public Computer getResult() {
return new Computer();
}
}

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.

Real-Life Software Applications


• SQL Query Builders: Used in ORMs (like Hibernate, SQLAlchemy, Laravel’s Eloquent) to dynamically construct SQL
queries with methods like select(), where(), orderBy(), etc.
• UI Component Builders: Useful in building dialog boxes, forms or widgets step by step by adding buttons, text fields or
styles.
• Document Builders: Used in tools like PDF/HTML generators, where content and styles are added step by step.
Adapter Design Pattern
Adapter Design Pattern is a structural pattern that acts as a bridge between two incompatible interfaces, allowing them to
work together. It is especially useful for integrating legacy code or third-party libraries into a new system.

Explain the Diagram:


• Client wants to use a Target interface (it calls Request() ).
• Adaptee already has useful functionality, but its method (SpecificRequest() ) doesn’t match the Target interface.
• Adapter acts as a bridge: it implements the Target interface (Request() ), but inside, it calls the Adaptee ’s SpecificRequest() .
• This allows the Client to use the Adaptee without changing its code.
Components of Adapter Design Pattern
Below are the components of adapter design pattern:
• Target Interface: The interface expected by the client, defining the operations it can use.
• Adaptee: The existing class with an incompatible interface that needs integration.
• Adapter: Implements the target interface and uses the adaptee internally, acting as a bridge.
• Client: Uses the target interface, unaware of the adapter or adaptee details.
Different implementations of Adapter Design Pattern
The Adapter Design Pattern can be applied in various ways depending on the programming language and the specific context.
Here are the primary implementations:

1. Class Adapter (Inheritance-based)


• In this approach, the adapter class inherits from both the target interface (the one the client expects) and the adaptee (the
existing class needing adaptation).
• Programming languages that allow multiple inheritance, like C++, are more likely to use this technique.
• However, in languages like Java and C#, which do not support multiple inheritance, this approach is less frequently used.

2. Object Adapter (Composition-based)


• The object adapter employs composition instead of inheritance. In this implementation, the adapter holds an instance of the
adaptee and implements the target interface.
• This approach is more flexible as it allows a single adapter to work with multiple adaptees and does not require the
complexities of inheritance.
• The object adapter is widely used in languages like Java and C#.
3. Two-way Adapter

• 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.

4. Interface Adapter (Default Adapter)

• 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.

How Adapter Design Pattern works?


• Step 1: The client initiates a request by calling a method on the adapter via the target interface.
• Step 2: The adapter maps or transforms the client's request into a format that the adaptee can understand using the
adaptee's interface.
• Step 3: The adaptee does the actual job based on the translated request from the adapter.
• Step 4: The client receives the results of the call, remaining unaware of the adapter's presence or the specific details of the
adaptee.
Adapter Design Pattern Example

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 */

2. Adaptee (LegacyPrinter) class LegacyPrinter {


public void printDocument() {
The existing class with an incompatible interface. System.out.println("Legacy Printer is printing a document.");
}
}
/* Adapter */

class PrinterAdapter implements Printer {


private LegacyPrinter legacyPrinter;
3. Adapter (PrinterAdapter) public PrinterAdapter(LegacyPrinter legacyPrinter) {
this.legacyPrinter = legacyPrinter;
The class that adapts the LegacyPrinter to the Printer interface.
}

@Override
public void print() {
legacyPrinter.printDocument();
}
}
Complete Code for the above example:

4. Client Code

The code that interacts with the Printer interface.

/* Client Code */
interface Printer {
void print();
}

void clientCode(Printer printer) {


printer.print();
}
Complete Code for the above example:

/* Adapter Design Pattern Example Code */ // Client Code


// Target Interface public class Client {
interface Printer { public static void clientCode(Printer printer) {
void print(); printer.print();
} }

// Adaptee public static void main(String[] args) {


class LegacyPrinter { // Using the Adapter
public void printDocument() { PrinterAdapter adapter = new PrinterAdapter();
System.out.println("Legacy Printer is printing a clientCode(adapter);
document."); }
} }
}

// 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.

•Keeps classes focused on core logic by isolating adaptation.

•Supports multiple interfaces through interchangeable adapters.

•Decouples system from implementations, easing modifications and swaps.

Cons of Adapter Design Pattern


Below are the cons of Adapter Design Pattern:
•Adds complexity and can make code harder to follow.

•Introduces slight performance overhead due to extra indirection.

•Multiple adapters increase maintenance effort.

•Risk of overuse for minor changes, leading to unnecessary complexity.

•Handling many interfaces may require multiple adapters, complicating design.


Decorator Design Pattern
Decorator Design Pattern is a structural pattern that lets you dynamically add behavior to individual objects without changing
other objects of the same class. It uses decorator classes to wrap concrete components, making functionality more flexible and
reusable.

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.

Key Components of the Decorator Design Pattern


• Component Interface: Defines common operations for components and decorators.
• Concrete Component: Core object with basic functionality.
• Decorator: Abstract wrapper that holds a Component reference and adds behavior.
• Concrete Decorator: Specific decorators that extend functionality of the component.
Example of Decorator Design Pattern
Suppose we are building a coffee shop application where customers can order different types of coffee. Each coffee can have
various optional add-ons such as milk, sugar, whipped cream, etc. We want to implement a system where we can dynamically
add these add-ons to a coffee order without modifying the coffee classes themselves.

Using the Decorator Pattern allows us to add optional features


(add-ons) to coffee orders dynamically without altering the core
coffee classes. This promotes code flexibility, scalability and
maintainability as new add-ons can be easily introduced and
combined with different types of coffee orders.
1. Component Interface(Coffee)
// Coffee.java
•This is the interface Coffee representing the component.
public interface Coffee {
•It declares two methods getDescription() and getCost() which must String getDescription();
be implemented by concrete components and decorators. double getCost();
}

2. ConcreteComponent(PlainCoffee) /*package whatever //do not write package name here */

•PlainCoffee is a concrete class implementing the Coffee import java.io.*;


interface.
class GFG {
•It provides the description and cost of plain coffee by public static void main (String[] args) {
implementing the getDescription() and getCost() methods. System.out.println("GFG!");
}
}
// PlainCoffee.java
3. Decorator(CoffeeDecorator)
public class PlainCoffee implements Coffee {
•CoffeeDecorator is an abstract class implementing the Coffee @Override
interface. public String getDescription() {
return "Plain Coffee";
•It maintains a reference to the decorated Coffee object. }

•The getDescription() and getCost() methods are implemented to @Override


delegate to the decorated coffee object. public double getCost() {
return 2.0;
}
}
4. ConcreteDecorators(MilkDecorator,SugarDecorator)

•MilkDecorator and SugarDecorator are concrete decorators


extending CoffeeDecorator.

•They override getDescription() to add the respective decorator


description to the decorated coffee's description.

•They override getCost() to add the cost of the respective


decorator to the decorated coffee's cost.
public interface Coffee { public class MilkDecorator extends CoffeeDecorator {
String getDescription(); public MilkDecorator(Coffee decoratedCoffee) {
double getCost(); super(decoratedCoffee);
}
}
@Override
public String getDescription() {
public abstract class CoffeeDecorator implements Coffee { return decoratedCoffee.getDescription() + ", Milk";
protected Coffee decoratedCoffee; }
public CoffeeDecorator(Coffee decoratedCoffee) { @Override
this.decoratedCoffee = decoratedCoffee; public double getCost() {
} return decoratedCoffee.getCost() + 0.5;
public String getDescription() { }
}
return decoratedCoffee.getDescription();
} public class SugarDecorator extends CoffeeDecorator {
public double getCost() { public SugarDecorator(Coffee decoratedCoffee) {
return decoratedCoffee.getCost(); super(decoratedCoffee);
} }
} Output @Override
Description: Plain Coffee public String getDescription() {
return decoratedCoffee.getDescription() + ", Sugar";
Cost: $ 2
}
Description: Plain Coffee, Milk @Override
Cost: $ 2.5 public double getCost() {
Description: Plain Coffee, Milk, Sugar return decoratedCoffee.getCost() + 0.2;
Cost: $ 2.7 }
}
Composite Design Pattern
The Composite Pattern is a structural design pattern that organizes objects into tree structures, enabling clients to treat
individual and composite objects uniformly through a common interface.
Use Cases of Composite Pattern
The Composite Pattern is useful in various scenarios, such as:
1. Graphics and GUI Libraries: Building complex graphical structures like shapes and groups.
2. File Systems: Representing files, directories, and their hierarchical relationships.
3. Organization Structures: Modeling hierarchical organizational structures like departments, teams and employees.
Example for Composite Design Pattern
You are tasked with developing a software component to manage a hierarchical file system structure. The goal is to
implement the Composite Pattern to seamlessly work with individual files and directories as part of a unified hierarchy.

Key Component in the Composite Pattern

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

In the file system hierarchy example, the Component is represented


abstract class FileSystemComponent {
by the FileSystemComponenet interface. This interface defines the
public abstract void display();
common interface for both leaf and composite objects. It declares a
}
method, display(), which all classes in the hierarchy must
implement.
The Component serves as the foundation for all objects within the hierarchy. Whether it's file or a directory, they all must
adhere to this common interface.
public class File extends FileSystemComponent {
private String name;
2. Leaf private int size;
In the context of our file system hierarchy public File(String name, int size) {
example, Leaf objects are the individual files. These are the this.name = name;
objects that do not have any children. Here is an implementation this.size = size;
of a leaf object, a file: }

@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<>();

public Directory(String name) {


this.name = name;
}

@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.

•Flexibility: Easily add or remove objects without affecting client code.

•Scalability: Supports nesting of composites for complex structures.

•Code Reusability: Same operations apply to both parts and wholes, reducing duplication.

Disadvantages of Composite Design Pattern


•Complex Implementation: Requires a common interface for all objects, making code more intricate.

•Performance Overhead: Traversing deep hierarchies can slow operations.

•Limited Type Safety: Common interface may allow invalid operations, risking runtime errors.

•Reduced Clarity: Code can become harder to understand in complex structures.

•Extra Memory Usage: Storing child references increases memory consumption.


Proxy Design Pattern
Proxy Design Pattern is a structural design pattern where a proxy object acts as a placeholder to control access to the real
object. The client communicates with the proxy, which forwards requests to the real object. The proxy can also provide extra
functionality such as access control, lazy initialization, logging, and caching.

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;

public ProxyImage(String filename) {


this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
4. Client Code:

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.

• Output: Loading + Displaying.

• Second call to display() → Proxy uses cached RealImage , no loading again.

• Output: Only Displaying.


Observer Design Pattern
Observer Design Pattern is a behavioral pattern that establishes a one-to-many dependency between objects. When the
subject changes its state, all its observers are automatically notified and updated. It focuses on enabling efficient
communication and synchronization between objects in response to state changes.

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

• Subject: Maintains a list of observers, provides methods to


add/remove them, and notifies them of state changes.

• Observer: Defines an interface with an update() method to


ensure all observers receive updates consistently.

• ConcreteSubject: A specific subject that holds actual data.


On state change, it notifies registered observers (e.g., a
weather station).

• ConcreteObserver: Implements the observer interface and


reacts to subject updates (e.g., a weather app showing
weather updates).
Observer Design Pattern Example
Consider a scenario where you have a weather monitoring system. Different parts of your application need to be updated
when the weather conditions change.

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.

How Observer Pattern helps to solve above challenges?

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

•The "Subject" interface outlines the operations a subject (like


public interface Subject {
"WeatherStation") should support.
void addObserver(Observer observer);
•"addObserver" and "removeObserver" are for managing the list void removeObserver(Observer observer);
of observers. void notifyObservers();
}
•"notifyObservers" is for informing observers about changes.

2. Observer

•The "Observer" interface defines a contract for objects that want


to be notified about changes in the subject ("WeatherStation" in
this case). public interface Observer {
void update(String weather);
•It includes a method "update" that concrete observers must }
implement to receive and handle updates.
import java.util.ArrayList;
import java.util.List;
3. ConcreteSubject(WeatherStation)
// Observer interface
interface Observer {
•"WeatherStation" is the concrete subject implementing the void update(String weather);
}
"Subject" interface.
// Subject interface
interface Subject {
•It maintains a list of observers ("observers") and provides void addObserver(Observer observer);
void removeObserver(Observer observer);
methods to manage this list. void notifyObservers();
}

•"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 = "";

•"setWeather" method updates the weather and notifies observers @Override


public void addObserver(Observer observer) {
of the change. observers.add(observer);
}

@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}

@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(weather);
}
}

public void setWeather(String newWeather) {


this.weather = newWeather;
notifyObservers();
}
}
import java.util.Observer;
import java.util.Observable;
4. ConcreteObserver(PhoneDisplay)

•"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. }
}

private void display() {


System.out.println("Phone Display: Weather updated - "
+ weather);
}
}
class TVDisplay implements Observer {
private String weather;
5. ConcreteObserver(TVDisplay)
@Override
•"TVDisplay" is another concrete observer similar to public void update(String weather) {
"PhoneDisplay". this.weather = weather;
display();
•It also implements the "Observer" interface, with a similar }
structure to "PhoneDisplay".
private void display() {
System.out.println("TV Display: Weather updated - " +
weather);
} public class WeatherApp {
} public static void main(String[] args) {
6. Usage WeatherStation weatherStation = new WeatherStation();

In "WeatherApp", a "WeatherStation" is created. Observer phoneDisplay = new PhoneDisplay();


Two observers ("PhoneDisplay" and "TVDisplay") are registered Observer tvDisplay = new TVDisplay();
with the weather station using "addObserver".
weatherStation.addObserver(phoneDisplay);
The "setWeather" method simulates a weather change to weatherStation.addObserver(tvDisplay);
"Sunny," triggering the "update" method in both observers.
The output shows how both concrete observers display the // Simulating weather change
updated weather information. weatherStation.setWeather("Sunny");

}
}
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.

Real Life Software Examples


• Multiple payment methods (Credit Card, PayPal, Bank Transfer, UPI etc.) on an E-Commerce site. Each payment
method has its own way to process, verify, handle failures, possibly different fees. Defining a PaymentStrategy interface
and concrete implementations for each payment method picks the right one based on user choice at runtime.

• 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.

Strategy Design Pattern Example(with implementation)


Let's consider a sorting application where we need to sort a list of integers. However, the sorting algorithm to be used
may vary depending on factors such as the size of the list and the desired performance characteristics.
Below is the implementation of the above example:

1. Context(SortingContext) public interface SortingStrategy {


void sort(int[] array);
}

public class SortingContext {


private SortingStrategy sortingStrategy;

public SortingContext(SortingStrategy sortingStrategy) {


this.sortingStrategy = sortingStrategy;
}

public void setSortingStrategy(SortingStrategy


sortingStrategy) {
this.sortingStrategy = sortingStrategy;
}

public void performSort(int[] array) {


sortingStrategy.sort(array);
}
}
public interface SortingStrategy {
2. Strategy Interface(SortingStrategy) void sort(int[] array);
// Implement sorting logic here
}
// BubbleSortStrategy
public class BubbleSortStrategy implements SortingStrategy {
@Override
public void sort(int[] array) {
// Implement Bubble Sort algorithm
3. Concrete Strategies System.out.println("Sorting using Bubble Sort");
}
}

// 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 BubbleSortStrategy implements SortingStrategy {


@Override
public void sort(String[] array) {
System.out.println("Sorting using Bubble Sort");
// Bubble sort implementation
}
}

class MergeSortStrategy implements SortingStrategy {


@Override
public void sort(String[] array) {
System.out.println("Sorting using Merge Sort");
// Merge sort implementation
}
}

class QuickSortStrategy implements SortingStrategy {


@Override
public void sort(String[] array) {
System.out.println("Sorting using Quick Sort");
// Quick sort implementation
}
}

class SortingContext {
private SortingStrategy strategy;

public SortingContext(SortingStrategy strategy) {


this.strategy = strategy;
}

public void performSort(String[] array) {


strategy.sort(array);
}

public void setSortingStrategy(SortingStrategy strategy) {


Output
Sorting using Bubble Sort
Sorting using Merge Sort
Sorting using Quick Sort
State Design Pattern
State Design Pattern is a behavioral design pattern that allows an object to change its behavior when its internal state changes.
This pattern is particularly useful when an object's behavior depends on its state, and the state can change during the object's
lifecycle.

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.

Components of State Design Pattern


• Context – Maintains a reference to the current state, delegates behavior to it, and provides an interface for clients.

• 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.

User Interaction with the System

User interactions with the vending machine trigger state


transitions. For example, when a user inserts money, the
vending machine transitions from the "ReadyState" to the
"PaymentPendingState." Similarly, when a product is selected,
the vending machine transitions to the "ProductSelectedState."
If a product is out of stock, the vending machine transitions to
the "OutOfStockState."
public class VendingMachineContext {
private VendingMachineState state;

public void setState(VendingMachineState state) {


1. Context(VendingMachineContext) this.state = state;
}
The context is responsible for maintaining the current state of
the vending machine and delegating state-specific behavior to public void request() {
state.handleRequest();
the appropriate state object. }
}

2. State Interface (VendingMachineState)

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();
}

public void setState(VendingMachineState state) {


this.currentState = state;
}

public void handleRequest() {


currentState.handleRequest();
}
}

// 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.

Communication between Components in the above example:

•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.

What is the Chain of Responsibility Design Pattern?


Chain of Responsibility Pattern or Chain of Responsibility Method is a Behavioral Design Pattern, which allows an
object to send a request to other objects without knowing who is going to handle it.
•This pattern is frequently used in the chain of multiple objects, where each object either handles the request or passes
it on to the next object in the chain if it is unable to handle that request.

•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.

How to Implement Chain of Responsibiility Design Pattern?


Below are the main steps for how to implement chain of responsibility design pattern:
•Step 1: Define the Handler Interface: Create an interface with methods for setting the next handler and processing requests.

•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

Implement the SupportHandler interface. Each handler is


responsible for handling requests based on its assigned priority
level. If a handler can handle the request, it processes it;
otherwise, it passes the request to the next handler in the chain.
public class Level1SupportHandler implements SupportHandler {
private SupportHandler nextHandler;

public void setNextHandler(SupportHandler nextHandler) {


this.nextHandler = nextHandler;
}

public void handleRequest(Request request) {


if (request.getPriority() == Priority.BASIC) {
System.out.println("Level 1 Support handled the request.");
} else if (nextHandler != null) {
nextHandler.handleRequest(request);
}
}
}

public class Level2SupportHandler implements SupportHandler {


private SupportHandler nextHandler; Level 1 Support handled the request.
public void setNextHandler(SupportHandler nextHandler) { Level 2 Support handled the request.
}
this.nextHandler = nextHandler;
Level 3 Support handled the request.
public void handleRequest(Request request) {
if (request.getPriority() == Priority.INTERMEDIATE) {
System.out.println("Level 2 Support handled the request.");
} else if (nextHandler != null) {
nextHandler.handleRequest(request);
}
}
}

public class Level3SupportHandler implements SupportHandler {


public void handleRequest(Request request) {
if (request.getPriority() == Priority.CRITICAL) {
System.out.println("Level 3 Support handled the request.");
} else {
System.out.println("Request cannot be handled.");
}
}

public void setNextHandler(SupportHandler nextHandler) {


// No next handler for Level 3
}
}
Applications of Chain of Responsibility Design Pattern
Below are the applications of chain of responsibility design pattern:
• In graphical user interfaces (GUIs), events like mouse clicks or key presses can be handled by a chain of listeners.
Each listener checks if it can handle the event, passing it along the chain if it can't. This way, multiple components
can respond to the same event without being tightly linked.

• 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.

You might also like