I'm always excited to take on new projects and collaborate with innovative minds.
contact@niteshsynergy.com
https://www.niteshsynergy.com/
Design patterns are common solutions to recurring design problems in software development. They represent best practices and offer reusable and adaptable solutions to commonly faced challenges in software architecture and design.
Design patterns are common solutions to recurring design problems in software development. They represent best practices and offer reusable and adaptable solutions to commonly faced challenges in software architecture and design.
Design patterns are reusable solutions to common software design problems. They improve readability, reusability, scalability, and maintainability. Key types include:
Creational (e.g., Singleton, Factory)
Structural (e.g., Adapter, Decorator)
Behavioral (e.g., Observer, Strategy)
They enhance code quality and team communication.
Rules for Singleton Pattern
7. Reflection Safety:
8. Preventing Cloning via the clone() Method:
9. Preventing Serialization Attack:
10. Using a Static Block (Eager Initialization with Reflection Safety):
11. Use of final Keyword:
Basic Singleton Implementation (Eager Initialization):
public class Singleton {
private static final Singleton INSTANCE = new Singleton(); // Static instance
private Singleton() {
// Private constructor
}
public static Singleton getInstance() {
return INSTANCE; // Global access point
}
}
Below code for Singleton breaking ways:
package com.niteshsynergy.designPattern.CreationalDesign;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
public class Demo1 {
public static void main(String[] args) throws Exception {
/* Singleton s1 = Singleton.getInstance();
System.out.println(s1.hashCode());
Singleton s2 = Singleton.getInstance();
System.out.println(s2.hashCode());
*/
BreakWithReflection.breakWithReflection();
BreakWithSerialization.breakWithSerialization();
BreakWithCloning.breakWithCloning();
BreakWithMultithreading.breakWithMultithreading();
BreakWithGarbageCollection.breakWithGarbageCollection();
BreakWithSubclassing.breakWithSubclassing();
}
//Prevention Strategies:
//Prevent Reflection: Add a check in the private constructor.
/* private Singleton() {
if (instance != null) {
throw new IllegalStateException("Instance already created!");
}
}*/
//Serialization Protection: Implement readResolve().
/* protected Object readResolve() {
return instance;
}*/
//Prevent Cloning: Override clone() and throw an exception.
//java
//Copy code
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException("Cannot clone singleton");
}
//Thread-Safe Initialization: Use Bill Pugh Singleton or Double-Checked Locking.
//Final Class: Declare the class as final to prevent subclassing.
}
//Code to break: 1. Reflection
class BreakWithReflection {
public static void breakWithReflection() {
try {
Singleton instance1 = Singleton.getInstance();
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true); // Bypass private access
Singleton instance2 = constructor.newInstance();
System.out.println(" From breakWithReflection");
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
}
//2. Serialization/Deserialization way break
class BreakWithSerialization {
public static void breakWithSerialization() throws Exception {
Singleton instance1 = Singleton.getInstance();
// Serialize instance
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
out.writeObject(instance1);
out.close();
// Deserialize instance
ObjectInputStream in = new ObjectInputStream(new FileInputStream("singleton.ser"));
Singleton instance2 = (Singleton) in.readObject();
in.close();
System.out.println(" From breakWithSerialization");
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
// 3. Cloning way break
class BreakWithCloning {
public static void breakWithCloning() throws CloneNotSupportedException {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = (Singleton) instance1.clone(); // If clone() is accessible
System.out.println(" From breakWithCloning");
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
// 4. Multithreading (Race Condition)
class BreakWithMultithreading {
public static void breakWithMultithreading() {
Runnable task = () -> {
Singleton instance = Singleton.getInstance();
System.out.println(instance.hashCode());
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
System.out.println(" From BreakWithMultithreading");
thread1.start();
thread2.start();
}
}
//5. Garbage Collection
class BreakWithGarbageCollection {
public static void breakWithGarbageCollection() {
Singleton instance = Singleton.getInstance();
WeakReference<Singleton> weakRef = new WeakReference<>(instance);
instance = null; // Nullify the strong reference
System.gc(); // Force garbage collection
System.out.println(" From breakWithGarbageCollection");
Singleton newInstance = Singleton.getInstance();
System.out.println(weakRef.get() == newInstance); // False if GC removed the weak reference
}
}
//6. Subclassing
class BreakWithSubclassing {
public static void breakWithSubclassing() {
// Singleton subclassInstance = new Singleton() {}; // Anonymous subclass
// System.out.println(subclassInstance.hashCode());
}
}
class Singleton{
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
// // If clone() is accessible, // 3. Cloning way break
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public void showMessage() {
System.out.println("Singleton Instance");
}
}
Design Pattern Types:
1. Creational Patterns
Creational patterns deal with object creation mechanisms, optimizing object creation based on requirements.
// Singleton Pattern
Use Case:
Database connection pool.
Logger class.
Configuration management in applications.
package com.niteshsynergy.designPattern.CreationalDesign;
import java.io.Serializable;
public final class Demo02PerfectSingleton implements Serializable, Cloneable {
// Private static inner class for lazy-loaded, thread-safe instance creation
private static class SingletonHelper {
private static final Demo02PerfectSingleton INSTANCE = new Demo02PerfectSingleton();
}
// Private constructor to prevent instantiation
private Demo02PerfectSingleton() {
if (SingletonHelper.INSTANCE != null) {
throw new IllegalStateException("Instance already created!");
}
}
// Public method to access the singleton instance
public static Demo02PerfectSingleton getInstance() {
return SingletonHelper.INSTANCE;
}
// Prevents instance creation during deserialization
protected Object readResolve() {
return getInstance();
}
// Prevents instance creation through cloning
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException("Cloning is not allowed for Singleton");
}
// Example method to demonstrate functionality
public void showMessage() {
System.out.println("Perfect Singleton Instance");
}
}
package com.niteshsynergy.designPattern.CreationalDesign;
public class Demo03FactoryPattern {
public static Payment getPayment(String type) {
return switch (type) {
case "CREDIT" -> new CreditCardPayment();
case "PAYPAL" -> new PayPalPayment();
default -> throw new IllegalArgumentException("Unknown payment type");
};
}
public static void main(String[] args) {
// Usage
Payment payment = Demo03FactoryPattern.getPayment("CREDIT");
payment.processPayment();
}
}
interface Payment {
void processPayment();
}
class CreditCardPayment implements Payment {
public void processPayment() {
System.out.println("Processing Credit Card Payment");
}
}
class PayPalPayment implements Payment {
public void processPayment() {
System.out.println("Processing PayPal Payment");
}
}
Factory Pattern
Concept
The Factory Pattern is a creational design pattern that provides an interface for creating objects in a superclass but allows subclasses to alter the type of objects that will be created. The idea is to encapsulate object creation to make the code more maintainable, scalable, and easier to test.
Key Concepts
Factory Method:
Centralized object creation.
Helps avoid tight coupling between client code and specific object types.
Encapsulation:
Encapsulates the logic for object creation in one place.
Polymorphism:
Enables returning objects that share a common interface but have different behaviors.
Use Case
Creation of different types of vehicles (Car, Bike, Truck).
Payment processing (CreditCard, PayPal, UPI).
Complex objects in a gaming application, such as Player, Enemy, and PowerUps.
public interface Shape {
void draw();
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Circle.");
}
}
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Rectangle.");
}
}
public class Square implements Shape {
@Override
public void draw() {
System.out.println("Drawing a Square.");
}
}
public class ShapeFactory {
public static Shape getShape(String shapeType) {
if (shapeType == null) {
throw new IllegalArgumentException("Shape type cannot be null");
}
switch (shapeType.toLowerCase()) {
case "circle":
return new Circle();
case "rectangle":
return new Rectangle();
case "square":
return new Square();
default:
throw new IllegalArgumentException("Unknown shape type: " + shapeType);
}
}
}
public class Main {
public static void main(String[] args) {
Shape circle = ShapeFactory.getShape("circle");
circle.draw();
Shape rectangle = ShapeFactory.getShape("rectangle");
rectangle.draw();
Shape square = ShapeFactory.getShape("square");
square.draw();
}
}
Output
Designing a car...
Manufacturing a car...
Designing a bike...
Manufacturing a bike...
Designing a truck...
Manufacturing a truck...
The Prototype Pattern is a creational design pattern that allows objects to be cloned instead of creating new instances from scratch. This is useful when creating an object is resource-intensive (e.g., involves complex initialization or database operations).
Step 1: Common Interface
public interface Prototype extends Cloneable {
Prototype clone();
}
Step 2: Concrete Prototype Classes
public class Circle implements Prototype {
private int radius;
public Circle(int radius) {
this.radius = radius;
}
public void setRadius(int radius) {
this.radius = radius;
}
@Override
public Prototype clone() {
return new Circle(this.radius);
}
@Override
public String toString() {
return "Circle with radius " + radius;
}
}
public class Rectangle implements Prototype {
private int width;
private int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
public void setDimensions(int width, int height) {
this.width = width;
this.height = height;
}
@Override
public Prototype clone() {
return new Rectangle(this.width, this.height);
}
@Override
public String toString() {
return "Rectangle [width=" + width + ", height=" + height + "]";
}
}
Step 3: Prototype Registry
import java.util.HashMap;
import java.util.Map;
public class PrototypeRegistry {
private Map<String, Prototype> prototypes = new HashMap<>();
public void addPrototype(String key, Prototype prototype) {
prototypes.put(key, prototype);
}
public Prototype getPrototype(String key) {
Prototype prototype = prototypes.get(key);
return prototype != null ? prototype.clone() : null;
}
}
Step 4: Client Code
public class Main {
public static void main(String[] args) {
// Create prototype objects
Circle circlePrototype = new Circle(10);
Rectangle rectanglePrototype = new Rectangle(20, 30);
// Register prototypes
PrototypeRegistry registry = new PrototypeRegistry();
registry.addPrototype("circle", circlePrototype);
registry.addPrototype("rectangle", rectanglePrototype);
// Create new objects by cloning
Circle clonedCircle = (Circle) registry.getPrototype("circle");
Rectangle clonedRectangle = (Rectangle) registry.getPrototype("rectangle");
// Modify clones
clonedCircle.setRadius(15);
clonedRectangle.setDimensions(40, 50);
// Print results
System.out.println(clonedCircle); // Circle with radius 15
System.out.println(clonedRectangle); // Rectangle [width=40, height=50]
}
}
A game has various entities (e.g., players, enemies, and weapons) with complex initialization logic. Instead of recreating entities, we clone pre-configured prototypes.
Step 1: Common Interface
public interface GameEntity extends Cloneable {
GameEntity clone();
void render();
}
Step 2: Concrete Entity Classes
public class Player implements GameEntity {
private String name;
private int health;
private int attackPower;
public Player(String name, int health, int attackPower) {
this.name = name;
this.health = health;
this.attackPower = attackPower;
}
public void setAttributes(String name, int health, int attackPower) {
this.name = name;
this.health = health;
this.attackPower = attackPower;
}
@Override
public GameEntity clone() {
return new Player(this.name, this.health, this.attackPower);
}
@Override
public void render() {
System.out.println("Player: " + name + ", Health: " + health + ", Attack Power: " + attackPower);
}
}
public class Enemy implements GameEntity {
private String type;
private int health;
public Enemy(String type, int health) {
this.type = type;
this.health = health;
}
public void setAttributes(String type, int health) {
this.type = type;
this.health = health;
}
@Override
public GameEntity clone() {
return new Enemy(this.type, this.health);
}
@Override
public void render() {
System.out.println("Enemy: " + type + ", Health: " + health);
}
}
Step 3: Prototype Registry
import java.util.HashMap;
import java.util.Map;
public class GameEntityRegistry {
private Map<String, GameEntity> prototypes = new HashMap<>();
public void addPrototype(String key, GameEntity entity) {
prototypes.put(key, entity);
}
public GameEntity getPrototype(String key) {
return prototypes.get(key).clone();
}
}
Step 4: Client Code
public class GameMain {
public static void main(String[] args) {
// Create prototypes
Player playerPrototype = new Player("Default Player", 100, 50);
Enemy enemyPrototype = new Enemy("Zombie", 75);
// Register prototypes
GameEntityRegistry registry = new GameEntityRegistry();
registry.addPrototype("player", playerPrototype);
registry.addPrototype("zombie", enemyPrototype);
// Clone and modify entities
Player player1 = (Player) registry.getPrototype("player");
player1.setAttributes("Player1", 120, 60);
Enemy zombie1 = (Enemy) registry.getPrototype("zombie");
zombie1.setAttributes("Zombie1", 80);
// Render entities
player1.render();
zombie1.render();
}
}
Output Example
Player: Player1, Health: 120, Attack Power: 60
Enemy: Zombie1, Health: 80
The Abstract Factory Pattern is a creational design pattern that provides an interface to create families of related or dependent objects without specifying their concrete classes. It works as a factory of factories.
public interface Button {
void render();
}
public interface Checkbox {
void render();
}
Step 2: Concrete Product Implementations
// Windows-specific products
public class WindowsButton implements Button {
@Override
public void render() {
System.out.println("Rendering Windows Button.");
}
}
public class WindowsCheckbox implements Checkbox {
@Override
public void render() {
System.out.println("Rendering Windows Checkbox.");
}
}
// Mac-specific products
public class MacButton implements Button {
@Override
public void render() {
System.out.println("Rendering Mac Button.");
}
}
public class MacCheckbox implements Checkbox {
@Override
public void render() {
System.out.println("Rendering Mac Checkbox.");
}
}
Step 3: Abstract Factory
public interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}
Step 4: Concrete Factories
public class WindowsFactory implements GUIFactory {
@Override
public Button createButton() {
return new WindowsButton();
}
@Override
public Checkbox createCheckbox() {
return new WindowsCheckbox();
}
}
public class MacFactory implements GUIFactory {
@Override
public Button createButton() {
return new MacButton();
}
@Override
public Checkbox createCheckbox() {
return new MacCheckbox();
}
}
Step 5: Client Code
public class Application {
private Button button;
private Checkbox checkbox;
public Application(GUIFactory factory) {
button = factory.createButton();
checkbox = factory.createCheckbox();
}
public void render() {
button.render();
checkbox.render();
}
public static void main(String[] args) {
// Windows GUI
GUIFactory windowsFactory = new WindowsFactory();
Application windowsApp = new Application(windowsFactory);
windowsApp.render();
// Mac GUI
GUIFactory macFactory = new MacFactory();
Application macApp = new Application(macFactory);
macApp.render();
}
}
Output Example
Rendering Windows Button.
Rendering Windows Checkbox.
Rendering Mac Button.
Rendering Mac Checkbox.
Complex Gaming Example: Weapon and Character Factories
Step 1: Abstract Product Interfaces
public interface Weapon {
void attack();
}
public interface Character {
void defend();
}
Step 2: Concrete Implementations
// Knight family
public class Sword implements Weapon {
@Override
public void attack() {
System.out.println("Swinging a Sword!");
}
}
public class Knight implements Character {
@Override
public void defend() {
System.out.println("Knight is defending!");
}
}
// Archer family
public class Bow implements Weapon {
@Override
public void attack() {
System.out.println("Shooting an Arrow!");
}
}
public class Archer implements Character {
@Override
public void defend() {
System.out.println("Archer is evading!");
}
}
Step 3: Abstract Factory
public interface EntityFactory {
Weapon createWeapon();
Character createCharacter();
}
Step 4: Concrete Factories
public class KnightFactory implements EntityFactory {
@Override
public Weapon createWeapon() {
return new Sword();
}
@Override
public Character createCharacter() {
return new Knight();
}
}
public class ArcherFactory implements EntityFactory {
@Override
public Weapon createWeapon() {
return new Bow();
}
@Override
public Character createCharacter() {
return new Archer();
}
}
Step 5: Client Code
public class Game {
public static void main(String[] args) {
EntityFactory knightFactory = new KnightFactory();
Weapon knightWeapon = knightFactory.createWeapon();
Character knight = knightFactory.createCharacter();
knightWeapon.attack();
knight.defend();
EntityFactory archerFactory = new ArcherFactory();
Weapon archerWeapon = archerFactory.createWeapon();
Character archer = archerFactory.createCharacter();
archerWeapon.attack();
archer.defend();
}
}
Output Example
Swinging a Sword!
Knight is defending!
Shooting an Arrow!
Archer is evading!
The Builder Pattern is a creational design pattern used to construct complex objects step by step. It separates the construction logic from the representation, ensuring that the same construction process can create different representations.
Step 1: Product Class
public class House {
private String foundation;
private String structure;
private String roof;
public void setFoundation(String foundation) {
this.foundation = foundation;
}
public void setStructure(String structure) {
this.structure = structure;
}
public void setRoof(String roof) {
this.roof = roof;
}
@Override
public String toString() {
return "House [Foundation=" + foundation + ", Structure=" + structure + ", Roof=" + roof + "]";
}
}
Step 2: Builder Interface
public interface HouseBuilder {
void buildFoundation();
void buildStructure();
void buildRoof();
House getHouse();
}
Step 3: Concrete Builders
public class WoodenHouseBuilder implements HouseBuilder {
private House house = new House();
@Override
public void buildFoundation() {
house.setFoundation("Wooden Foundation");
}
@Override
public void buildStructure() {
house.setStructure("Wooden Structure");
}
@Override
public void buildRoof() {
house.setRoof("Wooden Roof");
}
@Override
public House getHouse() {
return house;
}
}
public class StoneHouseBuilder implements HouseBuilder {
private House house = new House();
@Override
public void buildFoundation() {
house.setFoundation("Stone Foundation");
}
@Override
public void buildStructure() {
house.setStructure("Stone Structure");
}
@Override
public void buildRoof() {
house.setRoof("Stone Roof");
}
@Override
public House getHouse() {
return house;
}
}
Step 4: Director
public class HouseDirector {
private HouseBuilder builder;
public HouseDirector(HouseBuilder builder) {
this.builder = builder;
}
public House constructHouse() {
builder.buildFoundation();
builder.buildStructure();
builder.buildRoof();
return builder.getHouse();
}
}
Step 5: Client Code
public class BuilderDemo {
public static void main(String[] args) {
HouseBuilder woodenBuilder = new WoodenHouseBuilder();
HouseDirector director = new HouseDirector(woodenBuilder);
House woodenHouse = director.constructHouse();
System.out.println(woodenHouse);
HouseBuilder stoneBuilder = new StoneHouseBuilder();
director = new HouseDirector(stoneBuilder);
House stoneHouse = director.constructHouse();
System.out.println(stoneHouse);
}
}
Output Example
House [Foundation=Wooden Foundation, Structure=Wooden Structure, Roof=Wooden Roof]
House [Foundation=Stone Foundation, Structure=Stone Structure, Roof=Stone Roof]
package com.niteshsynergy.designPattern.StructuralDesign;
public class Demo01AdapterPattern {
//Scenario: Integrating Old Library with New System
/*
Imagine you are working with a new system that uses a set of modern USB-based devices, but you have an old system that
communicates with devices using serial ports (RS-232). The old system can't be directly connected to USB devices.
The Adapter Pattern can be used to adapt the old serial system interface to the new USB-based system by creating an adapter.
*/
public static void main(String[] args) {
// Old serial device
SerialDevice serialDevice = new SerialDevice();
// Using adapter to connect the serial device as if it were a USB device
USBDevice usbDevice = new SerialToUSBAdapter(serialDevice);
usbDevice.connectWithUSB(); // The new system now interacts with the old system via USB
}
/*
When to Use the Adapter Pattern
Incompatible Interfaces: When you need to integrate an existing class into a system that expects a different interface.
Legacy System Integration: When you have a legacy system or third-party library whose interface cannot be changed but needs to be used in a modern system.
Multiple Implementations: When you have different systems using different interfaces, but you want to create a unified way to communicate with all of them.
Decoupling Systems: When you want to decouple the client from the implementation details of the underlying system.
*/
}
//1. Target Interface (USBDevice)
interface USBDevice {
void connectWithUSB();
}
//2. Adaptee Class (SerialDevice)
class SerialDevice {
public void connectWithSerialPort() {
System.out.println("Connecting with serial port...");
}
}
//3. Adapter Class (SerialToUSBAdapter)
class SerialToUSBAdapter implements USBDevice {
private SerialDevice serialDevice;
public SerialToUSBAdapter(SerialDevice serialDevice) {
this.serialDevice = serialDevice;
}
@Override
public void connectWithUSB() {
System.out.println("Adapting to USB interface...");
serialDevice.connectWithSerialPort(); // Delegating call to the adaptee
}
}
/*
Use Case for Adapter Pattern
The Adapter Pattern is a structural design pattern that allows incompatible interfaces to work together. It acts as a bridge between two interfaces. The Adapter Pattern is often used when you have an existing class or library, but its interface is not compatible with the system you want to integrate it with.
*/
/*
Explanation of Code
Target Interface (USBDevice): This defines the interface that the new system expects. In this case, the new system communicates with devices that use the connectWithUSB() method.
Adaptee Class (SerialDevice): This is the existing class that provides the connectWithSerialPort() method. It's the old system or interface that is incompatible with the new one.
Adapter Class (SerialToUSBAdapter): This class implements the USBDevice interface, but internally it delegates the call to the SerialDevice class. It adapts the old serial device interface to the new USB interface by calling the connectWithSerialPort() method on the SerialDevice.
Client Code (Client): In the client code, the new system (which expects a USBDevice) interacts with the old system (which provides SerialDevice). The SerialToUSBAdapter adapts the old interface to the new one, so the client code doesn't need to change.
*/
ProxyPattern
package com.niteshsynergy.designPattern.StructuralDesign;
public class Demo02ProxyPattern {
//Scenario: Image Display with Proxy (Lazy Loading)
/*
Imagine you have a large image file that is not immediately needed by the client application. To optimize performance and reduce memory usage, you can use the Proxy Pattern to delay the loading of the image until it is actually required. This is known as lazy loading.
In this case, the Proxy acts as a placeholder that only loads the real image when necessary, while the client interacts with the proxy as if it were the actual image.
*/
public static void main(String[] args) {
Image image1 = new ProxyImage("image1.jpg");
Image image2 = new ProxyImage("image2.jpg");
// Image is loaded and displayed only when the `display` method is called
image1.display();
System.out.println();
// Image is already loaded, so only display it
image2.display();
}
}
//1. Subject Interface (Image)
interface Image {
void display();
}
//2. Real Subject (RealImage)
class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadImage();
}
private void loadImage() {
System.out.println("Loading image: " + fileName);
}
@Override
public void display() {
System.out.println("Displaying image: " + fileName);
}
}
//3. Proxy (ProxyImage)
class ProxyImage implements Image {
private RealImage realImage;
private String fileName;
public ProxyImage(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
// Only load and display the image if it's not already loaded
if (realImage == null) {
realImage = new RealImage(fileName);
}
realImage.display(); // Delegate to the real object
}
}
/*
Use Case for Proxy Pattern
The Proxy Pattern is a structural design pattern that provides an object representing another object. A proxy is used to control access to the original object, enabling additional functionality like lazy loading, access control, logging, or caching without changing the original object.
The Proxy Pattern can be particularly useful when:
You want to control access to a resource or object.
Performance optimization is required (e.g., lazy loading or caching).
Security is needed by controlling access permissions to a remote or sensitive resource.
Network communication is involved, where you want to act as an intermediary to access the actual service or resource.
*/
/*
Explanation of Code
Subject Interface (Image): This is the common interface that both the RealSubject and the Proxy implement. It defines the display() method that will be used by the client.
RealSubject (RealImage): This is the actual class that represents the real image. It is responsible for loading the image and displaying it. The image is loaded when the object is created, which can be resource-intensive.
Proxy (ProxyImage): The proxy class controls access to the RealSubject. It does not load the image until the display() method is called, implementing the lazy loading functionality. The proxy checks if the RealSubject has been instantiated before calling its display() method.
Client Code (ProxyPatternDemo): The client interacts with the ProxyImage as though it were the real image. The proxy ensures that the real image is loaded only when needed, optimizing resource usage.
*/
CompositePattern
package com.niteshsynergy.designPattern.StructuralDesign;
import java.util.ArrayList;
import java.util.List;
public class Demo03CompositePattern {
//Scenario: File System
/*
A file system is a classic example where the Composite Pattern can be applied.
In a file system, you can have files and directories (which can contain other files
and directories). The directories act as composite objects, while files are leaf objects. Both can be treated uniformly as items in the file system, allowing operations like displaying contents or calculating sizes to be applied to both individual files and directories, regardless of the hierarchy level.
*/
public static void main(String[] args) {
// Creating files
File file1 = new File("File1.txt", 10);
File file2 = new File("File2.jpg", 20);
File file3 = new File("File3.pdf", 30);
// Creating directories
Directory directory1 = new Directory("Documents");
Directory directory2 = new Directory("Images");
// Adding files to directories
directory1.addItem(file1);
directory1.addItem(file3);
directory2.addItem(file2);
// Creating the root directory and adding subdirectories
Directory rootDirectory = new Directory("Root");
rootDirectory.addItem(directory1);
rootDirectory.addItem(directory2);
// Displaying details of the entire directory structure
rootDirectory.showDetails();
}
}
//1. Component Interface (FileSystemItem)
interface FileSystemItem {
void showDetails();
}
//2. Leaf Class (File)
class File implements FileSystemItem {
private String name;
private long size;
public File(String name, long size) {
this.name = name;
this.size = size;
}
@Override
public void showDetails() {
System.out.println("File: " + name + " | Size: " + size + "KB");
}
}
//3. Composite Class (Directory)
class Directory implements FileSystemItem {
private String name;
private List<FileSystemItem> items = new ArrayList<>();
public Directory(String name) {
this.name = name;
}
public void addItem(FileSystemItem item) {
items.add(item);
}
public void removeItem(FileSystemItem item) {
items.remove(item);
}
@Override
public void showDetails() {
System.out.println("Directory: " + name);
for (FileSystemItem item : items) {
item.showDetails(); // Recursively show details of all items in the directory
}
}
}
/*
The Composite Pattern is a structural design pattern that allows you to compose objects into tree-like structures to represent part-whole hierarchies. It enables clients to treat individual objects and compositions of objects uniformly.
This pattern is useful when you have a hierarchy or tree structure of objects, where the individual objects (leaves) and the composite objects (branches) should be treated in the same way. This allows for building complex structures while maintaining simplicity in how they are accessed and interacted with.
*/
/*
Explanation of Code
Component Interface (FileSystemItem): This is the common interface that both leaf objects (like File) and composite objects (like Directory) implement. It defines the showDetails() method, which will be used to display the details of both files and directories.
Leaf Class (File): This represents individual objects (files) that do not contain other objects. It implements the FileSystemItem interface and provides the showDetails() method, which outputs the file's name and size.
Composite Class (Directory): This represents a collection of FileSystemItem objects, which could be both files and directories. It implements the FileSystemItem interface and provides the showDetails() method, which iterates through all items in the directory, calling their showDetails() method recursively.
Client Code (CompositePatternDemo): In the client code, files and directories are created and organized into a tree-like structure. The showDetails() method is called on the root directory, which then recursively displays the details of all files and subdirectories in the tree.
*/
FacadePattern
package com.niteshsynergy.designPattern.StructuralDesign;
public class Demo04FacadePattern {
//Scenario: Home Theater System
/*
Imagine you have a home theater system with several components such as a TV, DVD player, Sound System, Lights, and Air Conditioning. Each of these components has its own set of methods and configurations, and interacting with them individually can be complex.
A Facade can be used to create a HomeTheaterFacade that simplifies the process of turning on or off the home theater system. Instead of the client interacting with each component directly, it interacts with the Facade, which delegates the tasks to the appropriate components.
*/
public static void main(String[] args) {
// Creating subsystem objects
TV tv = new TV();
DVDPlayer dvdPlayer = new DVDPlayer();
SoundSystem soundSystem = new SoundSystem();
Lights lights = new Lights();
AirConditioner airConditioner = new AirConditioner();
// Creating the facade
HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade(tv, dvdPlayer, soundSystem, lights, airConditioner);
// Using the facade to watch a movie
homeTheaterFacade.watchMovie("Inception");
System.out.println("\nMovie has ended!");
// Using the facade to shut down the system
homeTheaterFacade.endMovie();
}
/*
Facade Pattern: Use Case & Code Example
Use Case for Facade Pattern
The Facade Pattern is a structural design pattern that provides a simplified interface to a complex subsystem, hiding its complexities from the client. The facade acts as a wrapper that delegates the client’s requests to the appropriate components of the subsystem, providing a cleaner and easier-to-use interface.
The Facade Pattern is particularly useful when:
You want to simplify interactions with a complex subsystem or set of APIs.
You want to reduce dependencies on external code by providing a higher-level interface.
You need to provide a unified interface to a set of related functionalities that are spread across multiple classes.
Scenario: Home Theater System
Imagine you have a home theater system with several components such as a TV, DVD player, Sound System, Lights, and Air Conditioning. Each of these components has its own set of methods and configurations, and interacting with them individually can be complex.
A Facade can be used to create a HomeTheaterFacade that simplifies the process of turning on or off the home theater system. Instead of the client interacting with each component directly, it interacts with the Facade, which delegates the tasks to the appropriate components.
Code Implementation
1. Subsystem Classes (TV, DVDPlayer, SoundSystem, etc.)
java
Copy code
// TV class
public class TV {
public void turnOn() {
System.out.println("TV is now ON");
}
public void turnOff() {
System.out.println("TV is now OFF");
}
}
// DVDPlayer class
public class DVDPlayer {
public void turnOn() {
System.out.println("DVD Player is now ON");
}
public void turnOff() {
System.out.println("DVD Player is now OFF");
}
public void playMovie(String movie) {
System.out.println("Playing movie: " + movie);
}
}
// SoundSystem class
public class SoundSystem {
public void turnOn() {
System.out.println("Sound System is now ON");
}
public void turnOff() {
System.out.println("Sound System is now OFF");
}
public void setVolume(int volume) {
System.out.println("Sound System volume set to " + volume);
}
}
// Lights class
public class Lights {
public void dimLights() {
System.out.println("Lights are dimmed");
}
public void turnOn() {
System.out.println("Lights are ON");
}
}
// AirConditioner class
public class AirConditioner {
public void turnOn() {
System.out.println("Air Conditioner is ON");
}
public void turnOff() {
System.out.println("Air Conditioner is OFF");
}
}
2. Facade Class (HomeTheaterFacade)
java
Copy code
public class HomeTheaterFacade {
private TV tv;
private DVDPlayer dvdPlayer;
private SoundSystem soundSystem;
private Lights lights;
private AirConditioner airConditioner;
public HomeTheaterFacade(TV tv, DVDPlayer dvdPlayer, SoundSystem soundSystem, Lights lights, AirConditioner airConditioner) {
this.tv = tv;
this.dvdPlayer = dvdPlayer;
this.soundSystem = soundSystem;
this.lights = lights;
this.airConditioner = airConditioner;
}
public void watchMovie(String movie) {
System.out.println("Get ready to watch a movie...");
lights.dimLights();
airConditioner.turnOn();
tv.turnOn();
dvdPlayer.turnOn();
dvdPlayer.playMovie(movie);
soundSystem.turnOn();
soundSystem.setVolume(5);
}
public void endMovie() {
System.out.println("Shutting down the home theater...");
lights.turnOn();
airConditioner.turnOff();
tv.turnOff();
dvdPlayer.turnOff();
soundSystem.turnOff();
}
}
3. Client Code (FacadePatternDemo)
java
Copy code
public class FacadePatternDemo {
public static void main(String[] args) {
// Creating subsystem objects
TV tv = new TV();
DVDPlayer dvdPlayer = new DVDPlayer();
SoundSystem soundSystem = new SoundSystem();
Lights lights = new Lights();
AirConditioner airConditioner = new AirConditioner();
// Creating the facade
HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade(tv, dvdPlayer, soundSystem, lights, airConditioner);
// Using the facade to watch a movie
homeTheaterFacade.watchMovie("Inception");
System.out.println("\nMovie has ended!");
// Using the facade to shut down the system
homeTheaterFacade.endMovie();
}
}
Explanation of Code
Subsystem Classes: These represent the complex components of the system, such as the TV, DVD Player, Sound System, Lights, and Air Conditioner. Each class has specific methods to turn the component on or off, and in the case of the DVD player, additional methods to play a movie.
Facade Class (HomeTheaterFacade): This class provides a simplified interface to the client. It aggregates the various subsystem objects and offers higher-level methods like watchMovie() and endMovie(), which internally invoke the corresponding actions on the subsystem objects. This way, the client does not need to deal with each component individually.
Client Code (FacadePatternDemo): The client uses the Facade to interact with the system. The client calls the watchMovie() method to set everything up (turning on the TV, playing the DVD, adjusting the sound system, etc.), and the endMovie() method to shut everything down. The client does not need to interact with each subsystem directly.
Sample Output
vbnet
Copy code
Get ready to watch a movie...
Lights are dimmed
Air Conditioner is ON
TV is now ON
DVD Player is now ON
Playing movie: Inception
Sound System is now ON
Sound System volume set to 5
Movie has ended!
Shutting down the home theater...
Lights are ON
Air Conditioner is OFF
TV is now OFF
DVD Player is now OFF
Sound System is now OFF
When to Use the Facade Pattern
Simplify Complex Subsystems: When you have a complex subsystem with many interdependent classes, the facade pattern simplifies the interaction with the subsystem by providing a unified, easy-to-use interface.
Reduce Client Dependency: When you want to minimize the number of classes a client needs to interact with. The facade pattern hides the complexities of the subsystem from the client, making the client code cleaner and easier to maintain.
Decouple Subsystems: When you want to decouple the client from the subsystems to avoid tight coupling. This allows subsystems to evolve independently without affecting the client code.
Group Related Functionality: When you have related classes (e.g., a set of components that together represent a subsystem) and you want to provide a simple interface for them.
Advantages of Facade Pattern
Simplifies Complex Systems: The facade pattern makes it easier to interact with complex subsystems by providing a simple interface.
Reduces Client Code Complexity: By using the facade, the client only interacts with one object, reducing the number of objects it needs to manage.
Loose Coupling: The client is decoupled from the subsystem, which makes it easier to modify or replace components of the subsystem without affecting the client.
Centralized Control: The facade provides a centralized location for managing interactions between the client and the subsystem, making the system easier to understand and maintain.
Disadvantages of Facade Pattern
Hides Subsystem Complexity: While the facade simplifies interaction with the system, it may also hide important details of the subsystem, making debugging or extending the system more challenging.
Single Point of Failure: The facade itself can become a bottleneck or a single point of failure if it does not delegate actions properly to the subsystem.
Real-World Examples of Facade Pattern
Home Automation Systems: In a smart home system, a facade might be used to control lights, temperature, security, and entertainment systems. The user interacts with a single interface to control everything without having to understand the underlying systems.
Database Access: A complex database system might have many classes for managing connections, transactions, queries, etc. A facade can provide a simple interface for the client to interact with the database without worrying about the internal complexities.
API Integration: When interacting with external services (like payment gateways, social media APIs, etc.), a facade can be used to simplify the integration process, providing a unified interface for various APIs that may have different communication protocols.
Video Games: In video games, a facade might control the interaction between various subsystems like graphics, physics, sound, and AI. The game developer interacts with the facade to trigger complex game behaviors without directly managing each subsystem.
*/
}
//1. Subsystem Classes (TV, DVDPlayer, SoundSystem, etc.)
// TV class
class TV {
public void turnOn() {
System.out.println("TV is now ON");
}
public void turnOff() {
System.out.println("TV is now OFF");
}
}
// DVDPlayer class
class DVDPlayer {
public void turnOn() {
System.out.println("DVD Player is now ON");
}
public void turnOff() {
System.out.println("DVD Player is now OFF");
}
public void playMovie(String movie) {
System.out.println("Playing movie: " + movie);
}
}
// SoundSystem class
class SoundSystem {
public void turnOn() {
System.out.println("Sound System is now ON");
}
public void turnOff() {
System.out.println("Sound System is now OFF");
}
public void setVolume(int volume) {
System.out.println("Sound System volume set to " + volume);
}
}
// Lights class
class Lights {
public void dimLights() {
System.out.println("Lights are dimmed");
}
public void turnOn() {
System.out.println("Lights are ON");
}
}
// AirConditioner class
class AirConditioner {
public void turnOn() {
System.out.println("Air Conditioner is ON");
}
public void turnOff() {
System.out.println("Air Conditioner is OFF");
}
}
//2. Facade Class (HomeTheaterFacade)
class HomeTheaterFacade {
private TV tv;
private DVDPlayer dvdPlayer;
private SoundSystem soundSystem;
private Lights lights;
private AirConditioner airConditioner;
public HomeTheaterFacade(TV tv, DVDPlayer dvdPlayer, SoundSystem soundSystem, Lights lights, AirConditioner airConditioner) {
this.tv = tv;
this.dvdPlayer = dvdPlayer;
this.soundSystem = soundSystem;
this.lights = lights;
this.airConditioner = airConditioner;
}
public void watchMovie(String movie) {
System.out.println("Get ready to watch a movie...");
lights.dimLights();
airConditioner.turnOn();
tv.turnOn();
dvdPlayer.turnOn();
dvdPlayer.playMovie(movie);
soundSystem.turnOn();
soundSystem.setVolume(5);
}
public void endMovie() {
System.out.println("Shutting down the home theater...");
lights.turnOn();
airConditioner.turnOff();
tv.turnOff();
dvdPlayer.turnOff();
soundSystem.turnOff();
}
}
/*
The Facade Pattern is a structural design pattern that provides a simplified interface to a complex subsystem, hiding its complexities from the client. The facade acts as a wrapper that delegates the client’s requests to the appropriate components of the subsystem, providing a cleaner and easier-to-use interface.
The Facade Pattern is particularly useful when:
You want to simplify interactions with a complex subsystem or set of APIs.
You want to reduce dependencies on external code by providing a higher-level interface.
You need to provide a unified interface to a set of related functionalities that are spread across multiple classes.
*/
/*
Explanation of Code
Subsystem Classes: These represent the complex components of the system, such as the TV, DVD Player, Sound System, Lights, and Air Conditioner. Each class has specific methods to turn the component on or off, and in the case of the DVD player, additional methods to play a movie.
Facade Class (HomeTheaterFacade): This class provides a simplified interface to the client. It aggregates the various subsystem objects and offers higher-level methods like watchMovie() and endMovie(), which internally invoke the corresponding actions on the subsystem objects. This way, the client does not need to deal with each component individually.
Client Code (FacadePatternDemo): The client uses the Facade to interact with the system. The client calls the watchMovie() method to set everything up (turning on the TV, playing the DVD, adjusting the sound system, etc.), and the endMovie() method to shut everything down. The client does not need to interact with each subsystem directly.
*/
FlyweightPattern
package com.niteshsynergy.designPattern.StructuralDesign;
import java.util.HashMap;
import java.util.Map;
public class Demo05FlyweightPattern {
//Scenario: Text Formatting System
/*
Imagine you are building a text formatting system that displays large amounts of text. Each character in the text could be an object, but each character only differs in terms of the character itself (e.g., 'a', 'b', 'c') and the formatting style (e.g., bold, italic). Instead of creating a new object for each character every time a new document is created, the Flyweight Pattern can be used to store the shared character data once and reuse it across multiple text documents.
*/
public static void main(String[] args) {
// Reusing Flyweights
Character charA = CharacterFactory.getCharacter('A');
Character charB = CharacterFactory.getCharacter('B');
Character charC = CharacterFactory.getCharacter('C');
// Contextual state: applying styles
TextStyle styleBold = new TextStyle(true, false);
TextStyle styleItalic = new TextStyle(false, true);
// Applying styles to the characters
styleBold.applyStyle(charA);
styleItalic.applyStyle(charB);
styleBold.applyStyle(charC);
// Reusing the same 'A' character object
Character charA2 = CharacterFactory.getCharacter('A');
styleItalic.applyStyle(charA2);
// Checking if the reused object is the same as the original 'A'
System.out.println("Are both 'A' characters the same instance? " + (charA == charA2));
}
/*
Flyweight Pattern: Use Case & Code Example
Use Case for Flyweight Pattern
The Flyweight Pattern is a structural design pattern that is used to minimize memory usage by sharing as much data as possible between objects. The Flyweight pattern is particularly useful when there are many objects that are similar in nature and require similar resources, but where maintaining a large number of individual objects would be inefficient.
The Flyweight Pattern is most useful when:
You have a large number of objects that share common properties or behaviors.
You want to reduce the memory consumption of an application by reusing shared objects instead of creating new ones each time.
You can break down the object’s state into two parts: intrinsic (shared) and extrinsic (contextual) states.
The Flyweight Pattern essentially allows you to share the intrinsic state (which is common across objects) and separate the extrinsic state (which is unique to each object) that can be passed into the Flyweight objects when needed.
Scenario: Text Formatting System
Imagine you are building a text formatting system that displays large amounts of text. Each character in the text could be an object, but each character only differs in terms of the character itself (e.g., 'a', 'b', 'c') and the formatting style (e.g., bold, italic). Instead of creating a new object for each character every time a new document is created, the Flyweight Pattern can be used to store the shared character data once and reuse it across multiple text documents.
Code Implementation
1. Flyweight Class (Character)
java
Copy code
// Flyweight class
public class Character {
private char symbol;
public Character(char symbol) {
this.symbol = symbol;
}
public char getSymbol() {
return symbol;
}
// The Flyweight class has the intrinsic state (symbol) that is shared.
}
2. Flyweight Factory Class (CharacterFactory)
java
Copy code
import java.util.HashMap;
import java.util.Map;
// Flyweight Factory class
public class CharacterFactory {
private static final Map<Character, Character> characterPool = new HashMap<>();
// Retrieves the Flyweight (reusing if already created)
public static Character getCharacter(char symbol) {
Character character = characterPool.get(symbol);
if (character == null) {
character = new Character(symbol);
characterPool.put(symbol, character);
System.out.println("Creating new character: " + symbol);
}
return character;
}
}
3. Contextual State Class (TextStyle)
java
Copy code
// Contextual state class
public class TextStyle {
private boolean bold;
private boolean italic;
public TextStyle(boolean bold, boolean italic) {
this.bold = bold;
this.italic = italic;
}
public void applyStyle(Character character) {
System.out.print("Character " + character.getSymbol());
if (bold) {
System.out.print(" in Bold");
}
if (italic) {
System.out.print(" in Italic");
}
System.out.println();
}
}
4. Client Code (FlyweightPatternDemo)
java
Copy code
public class FlyweightPatternDemo {
public static void main(String[] args) {
// Reusing Flyweights
Character charA = CharacterFactory.getCharacter('A');
Character charB = CharacterFactory.getCharacter('B');
Character charC = CharacterFactory.getCharacter('C');
// Contextual state: applying styles
TextStyle styleBold = new TextStyle(true, false);
TextStyle styleItalic = new TextStyle(false, true);
// Applying styles to the characters
styleBold.applyStyle(charA);
styleItalic.applyStyle(charB);
styleBold.applyStyle(charC);
// Reusing the same 'A' character object
Character charA2 = CharacterFactory.getCharacter('A');
styleItalic.applyStyle(charA2);
// Checking if the reused object is the same as the original 'A'
System.out.println("Are both 'A' characters the same instance? " + (charA == charA2));
}
}
Explanation of Code
Flyweight Class (Character): The Character class represents the intrinsic state, which is shared across all instances. In this case, it only contains the character symbol (char). Each Character object is created once and can be reused across different contexts.
Flyweight Factory Class (CharacterFactory): This class is responsible for managing the creation and reuse of the Character objects. It keeps a cache (characterPool) of already created Character objects and returns the same object if one with the same symbol already exists. If not, it creates a new object, adds it to the cache, and returns it.
Contextual State Class (TextStyle): The TextStyle class represents the extrinsic state (context-specific data). This includes formatting details such as whether the text should be bold or italic. This information is passed at runtime and is not part of the Flyweight object.
Client Code (FlyweightPatternDemo): The client code demonstrates how Flyweights are shared. Characters A, B, and C are created and styled. The system reuses the A character object when needed. The client can apply different styles (extrinsic state) without creating new instances for each unique combination.
*/
}
//1. Flyweight Class (Character)
// Flyweight class
class Character {
private java.lang.Character symbol;
public Character(char symbol) {
this.symbol = symbol;
}
public char getSymbol() {
return symbol;
}
// The Flyweight class has the intrinsic state (symbol) that is shared.
}
//2. Flyweight Factory Class (CharacterFactory)
// Flyweight Factory class
class CharacterFactory {
private static final Map<Character, Character> characterPool = new HashMap<>();
// Retrieves the Flyweight (reusing if already created)
public static Character getCharacter(char symbol) {
Character character = characterPool.get(symbol);
if (character == null) {
character = new Character(symbol);
// characterPool.put(symbol, character);
System.out.println("Creating new character: " + symbol);
}
return character;
}
}
//3. Contextual State Class (TextStyle)
// Contextual state class
class TextStyle {
private boolean bold;
private boolean italic;
public TextStyle(boolean bold, boolean italic) {
this.bold = bold;
this.italic = italic;
}
public void applyStyle(Character character) {
System.out.print("Character " + character.getSymbol());
if (bold) {
System.out.print(" in Bold");
}
if (italic) {
System.out.print(" in Italic");
}
System.out.println();
}
}
/*
Use Case for Flyweight Pattern
The Flyweight Pattern is a structural design pattern that is used to minimize memory usage by sharing as much data as possible between objects. The Flyweight pattern is particularly useful when there are many objects that are similar in nature and require similar resources, but where maintaining a large number of individual objects would be inefficient.
The Flyweight Pattern is most useful when:
You have a large number of objects that share common properties or behaviors.
You want to reduce the memory consumption of an application by reusing shared objects instead of creating new ones each time.
You can break down the object’s state into two parts: intrinsic (shared) and extrinsic (contextual) states.
*/
BridgePattern
package com.niteshsynergy.designPattern.StructuralDesign;
public class Demo06BridgePattern {
//Scenario: Shape and Color
/*
Consider a scenario where you are designing a drawing application that deals with different shapes (e.g., Circle, Rectangle) and colors (e.g., Red, Blue). Without the Bridge Pattern, you'd end up with a large number of shape-color combinations like RedCircle, BlueCircle, RedRectangle, BlueRectangle, and so on. The Bridge Pattern allows you to separate the shape and color functionalities into different classes, avoiding this explosion of classes.
*/
public static void main(String[] args) {
Shape redCircle = new Circle(new Red(), 10);
Shape blueCircle = new Circle(new Blue(), 15);
redCircle.draw();
blueCircle.draw();
}
}
//1. Abstraction Class (Shape)
// Abstraction class
abstract class Shape {
protected Color color; // Reference to the Implementor class
// Constructor to initialize the Implementor (Color)
protected Shape(Color color) {
this.color = color;
}
// Abstract method for drawing shapes, to be implemented by subclasses
public abstract void draw();
}
//2. Refined Abstraction Class (Circle)
// Refined abstraction class
class Circle extends Shape {
private int radius;
public Circle(Color color, int radius) {
super(color); // Passing the implementor (Color)
this.radius = radius;
}
@Override
public void draw() {
System.out.print("Drawing Circle with radius " + radius + " in ");
color.applyColor(); // Delegating the color functionality to the Implementor
}
}
//3. Implementor Interface (Color)
// Implementor interface
interface Color {
void applyColor();
}
//4. Concrete Implementor Classes (Red and Blue)
// Concrete Implementor classes
class Red implements Color {
@Override
public void applyColor() {
System.out.println("Red color");
}
}
class Blue implements Color {
@Override
public void applyColor() {
System.out.println("Blue color");
}
}
/*
Use Case for Bridge Pattern
The Bridge Pattern is a structural design pattern that is used to separate an abstraction from its implementation so that both can be varied independently. The main goal of the Bridge Pattern is to decouple abstraction and implementation, allowing both to evolve without affecting each other.
The Bridge Pattern is useful when:
You want to avoid creating a large number of classes due to combinations of different abstraction and implementation classes.
You need to modify an abstraction and its implementation independently.
You have several variants of a class, and the differences lie mainly in the details of the implementation, not in the abstraction itself.
*/
/*
Explanation of Code
Abstraction (Shape): This class contains a reference to the Color interface (implementor). It defines the core behavior (draw) that can be refined by subclasses. It allows the Shape class to remain independent of the color implementation.
Refined Abstraction (Circle): The Circle class extends Shape and implements the draw() method. It delegates the color-specific logic to the Color object, which implements the actual color functionality.
Implementor Interface (Color): The Color interface defines the applyColor() method, which is implemented by concrete color classes like Red and Blue.
Concrete Implementors (Red and Blue): The concrete Red and Blue classes implement the applyColor() method, providing the color-specific functionality.
Client Code (BridgePatternDemo): The client code creates instances of Shape (e.g., Circle) and assigns different Color implementations (e.g., Red, Blue) to them. The draw() method is called, which delegates the color application to the respective color object.
*/
DecoratorPattern
package com.niteshsynergy.designPattern.StructuralDesign;
public class Demo07DecoratorPattern {
//Scenario: Coffee Shop
/*
Consider a coffee shop where customers can order a basic coffee (Coffee), and depending on their choice, additional toppings (like milk, sugar, chocolate) can be added. Instead of creating a new subclass for every possible combination of coffee and toppings, the Decorator Pattern allows you to decorate a basic Coffee object with various toppings.
*/
public static void main(String[] args) {
Coffee coffee = new BasicCoffee();
System.out.println("Cost of Basic Coffee: " + coffee.cost());
Coffee milkCoffee = new MilkDecorator(coffee);
System.out.println("Cost of Coffee with Milk: " + milkCoffee.cost());
Coffee milkSugarCoffee = new SugarDecorator(milkCoffee);
System.out.println("Cost of Coffee with Milk and Sugar: " + milkSugarCoffee.cost());
}
}
//1. Component Interface (Coffee)
// Component interface
interface Coffee {
double cost();
}
//2. Concrete Component Class (BasicCoffee)
// Concrete Component class
class BasicCoffee implements Coffee {
@Override
public double cost() {
return 5.0; // Basic coffee price
}
}
//3. Decorator Class (CoffeeDecorator)
// Decorator class
abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee; // The coffee object to be decorated
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
public double cost() {
return decoratedCoffee.cost();
}
}
//4. Concrete Decorators (MilkDecorator, SugarDecorator)
// Concrete Decorator for Milk
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public double cost() {
return decoratedCoffee.cost() + 1.5; // Adding milk cost
}
}
// Concrete Decorator for Sugar
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public double cost() {
return decoratedCoffee.cost() + 0.5; // Adding sugar cost
}
}
/*
Use Case for Decorator Pattern
The Decorator Pattern is a structural design pattern that allows you to add new functionality to an object dynamically without altering its structure. It provides a flexible alternative to subclassing for extending functionality.
The Decorator Pattern is useful when:
You want to add responsibilities to objects without affecting other objects of the same class.
You need to extend an object's behavior in a flexible and reusable way, rather than using inheritance.
You want to allow behaviors to be added at runtime (as opposed to at compile-time).
*/
/*
Explanation of Code
Component Interface (Coffee): This is the interface that defines the behavior (cost()) that all concrete and decorator classes should implement.
Concrete Component (BasicCoffee): This is the basic coffee object that implements the Coffee interface and defines the base cost.
Decorator Class (CoffeeDecorator): This is an abstract decorator class that implements the Coffee interface and holds a reference to a Coffee object. It delegates the cost() method to the wrapped Coffee object, allowing additional behavior to be added by the concrete decorators.
Concrete Decorators (MilkDecorator, SugarDecorator): These concrete decorators add additional functionality (costs) to the basic coffee object. They override the cost() method to include their specific cost.
Client Code (DecoratorPatternDemo): The client code demonstrates how the decorator pattern can be used to dynamically add functionality to a Coffee object, such as adding milk and sugar. The base BasicCoffee object is decorated with various decorators at runtime.
*/
Behavioral Design Pattern
TemplateMethodPattern
package com.niteshsynergy.designPattern.BehavioralDesign;
public class Demo01TemplateMethodPattern {
//Scenario: Preparing a Beverage
/*
Consider a scenario where we are designing a system for preparing beverages like tea and coffee. Both tea and coffee require some common steps, such as boiling water and pouring it into a cup, but they also require different steps (e.g., tea requires steeping, while coffee requires brewing). Using the Template Method Pattern, we can define a general template for preparing a beverage, while allowing each subclass to implement the specific steps for tea or coffee.
*/
public static void main(String[] args) {
Beverage tea = new Tea();
Beverage coffee = new Coffee();
System.out.println("Preparing tea...");
tea.prepareRecipe();
System.out.println("\nPreparing coffee...");
coffee.prepareRecipe();
}
/*
Explanation of Code
Abstract Class (Beverage): This is the class that defines the template method (prepareRecipe()). The template method contains the steps of the algorithm. Some of these steps are common to all beverages, such as boiling water and pouring into a cup, while others (e.g., brewing and adding condiments) are abstract and are meant to be implemented by subclasses.
Concrete Class for Tea (Tea): This class provides the specific implementation of the abstract methods for making tea, such as steeping the tea and adding lemon.
Concrete Class for Coffee (Coffee): This class provides the specific implementation of the abstract methods for making coffee, such as dripping coffee through a filter and adding sugar and milk.
Client Code (TemplateMethodPatternDemo): The client code creates instances of Tea and Coffee, and calls the prepareRecipe() method on both objects. Despite the different beverage types, the overall algorithm remains the same, while the specific steps (e.g., brewing and adding condiments) differ.
*/
}
//1. Abstract Class (Beverage)
// Abstract class defining the template method and common behavior
abstract class Beverage {
// Template Method
public final void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
// Common step to boil water
private void boilWater() {
System.out.println("Boiling water...");
}
// Abstract method for brewing the beverage, to be implemented by subclasses
protected abstract void brew();
// Common step to pour beverage into the cup
private void pourInCup() {
System.out.println("Pouring into cup...");
}
// Abstract method for adding condiments, to be implemented by subclasses
protected abstract void addCondiments();
}
//2. Concrete Class for Tea (Tea)
// Concrete class for tea
class Tea extends Beverage {
@Override
protected void brew() {
System.out.println("Steeping the tea...");
}
@Override
protected void addCondiments() {
System.out.println("Adding lemon...");
}
}
//3. Concrete Class for Coffee (Coffee)
// Concrete class for coffee
class Coffee extends Beverage {
@Override
protected void brew() {
System.out.println("Dripping coffee through filter...");
}
@Override
protected void addCondiments() {
System.out.println("Adding sugar and milk...");
}
}
/*
The Template Method Pattern is useful when:
You have an algorithm that must be implemented in multiple steps.
Some steps of the algorithm may vary across subclasses, but the overall algorithm should remain the same.
You want to allow subclasses to redefine certain parts of the algorithm while keeping the overall process intact.
*/
MediatorPattern
package com.niteshsynergy.designPattern.BehavioralDesign;
import java.util.ArrayList;
import java.util.List;
public class Demo02MediatorPattern {
public static void main(String[] args) {
ChatMediator mediator = new ChatRoom();
User user1 = new User("Alice", mediator);
User user2 = new User("Bob", mediator);
mediator.addUser(user1);
mediator.addUser(user2);
user1.sendMessage("Hello, Bob!");
}
}
// Mediator interface
interface ChatMediator {
void sendMessage(String message, User user);
void addUser(User user);
}
// Concrete Mediator
class ChatRoom implements ChatMediator {
private List<User> users = new ArrayList<>();
@Override
public void sendMessage(String message, User user) {
for (User u : users) {
if (u != user) {
u.receiveMessage(message);
}
}
}
@Override
public void addUser(User user) {
users.add(user);
}
}
// Colleague class
class User {
private String name;
private ChatMediator mediator;
public User(String name, ChatMediator mediator) {
this.name = name;
this.mediator = mediator;
}
public void sendMessage(String message) {
mediator.sendMessage(message, this);
}
public void receiveMessage(String message) {
System.out.println(name + " received: " + message);
}
}
/*
Example Use Case: In a chat application, instead of users directly communicating with each other, a Mediator (like a ChatRoom) handles all interactions, ensuring proper message routing.
*/
ObserverPattern
package com.niteshsynergy.designPattern.BehavioralDesign;
import java.util.ArrayList;
import java.util.List;
public class Demo04ObserverPattern {
public static void main(String[] args) {
WeatherStation station = new WeatherStation();
TemperatureDisplay display = new TemperatureDisplay();
station.addObserver(display);
station.setTemperature(25);
}
}
// Subject
class WeatherStation {
private List<Observer> observers = new ArrayList<>();
private int temperature;
public void addObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void setTemperature(int temperature) {
this.temperature = temperature;
notifyObservers();
}
private void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature);
}
}
}
// Observer
interface Observer {
void update(int temperature);
}
// Concrete Observer
class TemperatureDisplay implements Observer {
private int temperature;
@Override
public void update(int temperature) {
this.temperature = temperature;
System.out.println("Temperature updated to: " + temperature);
}
}
/*
Example Use Case: A weather monitoring system where multiple devices (observers) need to be updated whenever the weather data (subject) changes.
*/
StrategyPattern
package com.niteshsynergy.designPattern.BehavioralDesign;
public class Demo05StrategyPattern {
public static void main(String[] args) {
PaymentContext context = new PaymentContext(new CreditCardPayment());
context.executePayment(100);
context = new PaymentContext(new PayPalPayment());
context.executePayment(200);
}
}
// Strategy interface
interface PaymentStrategy {
void pay(int amount);
}
// Concrete Strategies
class CreditCardPayment implements PaymentStrategy {
@Override
public void pay(int amount) {
System.out.println("Paid " + amount + " using Credit Card");
}
}
class PayPalPayment implements PaymentStrategy {
@Override
public void pay(int amount) {
System.out.println("Paid " + amount + " using PayPal");
}
}
// Context
class PaymentContext {
private PaymentStrategy paymentStrategy;
public PaymentContext(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void executePayment(int amount) {
paymentStrategy.pay(amount);
}
}
/*
Example Use Case: In a payment system where customers can pay via different methods (credit card, PayPal, etc.), and the client selects the appropriate payment method at runtime.
*/
CommandPattern
package com.niteshsynergy.designPattern.BehavioralDesign;
public class Demo06CommandPattern {
public static void main(String[] args) {
Light light = new Light();
Command lightOn = new LightOnCommand(light);
RemoteControl remote = new RemoteControl();
remote.setCommand(lightOn);
remote.pressButton();
}
}
// Command interface
interface Command {
void execute();
}
// Concrete Command
class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOn();
}
}
// Receiver class
class Light {
public void turnOn() {
System.out.println("Light is ON");
}
public void turnOff() {
System.out.println("Light is OFF");
}
}
// Invoker class
class RemoteControl {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
}
}
/*
Example Use Case: In a remote control system where commands for turning devices on or off are encapsulated and executed based on user input.
*/
StatePattern
package com.niteshsynergy.designPattern.BehavioralDesign;
public class Demo07StatePattern {
public static void main(String[] args) {
VendingMachine vendingMachine = new VendingMachine();
vendingMachine.insertCoin();
vendingMachine.dispenseItem();
}
}
// State interface
interface VendingMachineState {
void insertCoin();
void ejectCoin();
void dispenseItem();
}
// Concrete States
class NoCoinState implements VendingMachineState {
private VendingMachine vendingMachine;
public NoCoinState(VendingMachine vendingMachine) {
this.vendingMachine = vendingMachine;
}
@Override
public void insertCoin() {
System.out.println("Coin inserted.");
vendingMachine.setState(vendingMachine.getHasCoinState());
}
@Override
public void ejectCoin() {
System.out.println("No coin to eject.");
}
@Override
public void dispenseItem() {
System.out.println("Insert coin first.");
}
}
class HasCoinState implements VendingMachineState {
private VendingMachine vendingMachine;
public HasCoinState(VendingMachine vendingMachine) {
this.vendingMachine = vendingMachine;
}
@Override
public void insertCoin() {
System.out.println("Coin already inserted.");
}
@Override
public void ejectCoin() {
System.out.println("Coin ejected.");
vendingMachine.setState(vendingMachine.getNoCoinState());
}
@Override
public void dispenseItem() {
System.out.println("Dispensing item.");
vendingMachine.setState(vendingMachine.getNoCoinState());
}
}
// Context (VendingMachine)
class VendingMachine {
private VendingMachineState noCoinState;
private VendingMachineState hasCoinState;
private VendingMachineState currentState;
public VendingMachine() {
noCoinState = new NoCoinState(this);
hasCoinState = new HasCoinState(this);
currentState = noCoinState;
}
public void setState(VendingMachineState state) {
currentState = state;
}
public void insertCoin() {
currentState.insertCoin();
}
public void ejectCoin() {
currentState.ejectCoin();
}
public void dispenseItem() {
currentState.dispenseItem();
}
public VendingMachineState getNoCoinState() {
return noCoinState;
}
public VendingMachineState getHasCoinState() {
return hasCoinState;
}
}
/*
Example Use Case: In a vending machine, the behavior (e.g., accepting money, dispensing item) changes based on the current state (e.g., idle, money inserted, item dispensed).
*/
VisitorPattern
package com.niteshsynergy.designPattern.BehavioralDesign;
public class Demo08VisitorPattern {
}
/*
Example Use Case: In a shopping cart system where various types of items (e.g., books, electronics) need to be processed differently, but the processing behavior can change dynamically.
*/
InterpreterPattern
package com.niteshsynergy.designPattern.BehavioralDesign;
public class Demo09InterpreterPattern {
}
/*
Use Case:
The Interpreter Pattern is used when you need to evaluate sentences or expressions in a language, especially useful for designing interpreters or compilers.
*/
IteratorPattern
package com.niteshsynergy.designPattern.BehavioralDesign;
public class Demo10IteratorPattern {
}
/*
Use Case:
The Iterator Pattern is used to provide a way to access elements of a collection sequentially without exposing its underlying representation.
*/
ChainofResponsibilityPattern
package com.niteshsynergy.designPattern.BehavioralDesign;
public class Demp03ChainofResponsibilityPattern {
public static void main(String[] args) {
SupportHandler handler1 = new LevelOneSupport();
SupportHandler handler2 = new LevelTwoSupport();
handler1.setNextHandler(handler2);
handler1.handleRequest("simple issue");
handler1.handleRequest("complex issue");
}
}
// Abstract Handler
abstract class SupportHandler {
protected SupportHandler nextHandler;
public void setNextHandler(SupportHandler nextHandler) {
this.nextHandler = nextHandler;
}
public abstract void handleRequest(String request);
}
// Concrete Handlers
class LevelOneSupport extends SupportHandler {
@Override
public void handleRequest(String request) {
if (request.equals("simple issue")) {
System.out.println("Level One Support handling the request.");
} else {
System.out.println("Passing to Level Two Support.");
nextHandler.handleRequest(request);
}
}
}
class LevelTwoSupport extends SupportHandler {
@Override
public void handleRequest(String request) {
if (request.equals("complex issue")) {
System.out.println("Level Two Support handling the request.");
} else {
System.out.println("No one can handle this request.");
}
}
}
/*
Example Use Case: In a support system where a request is passed through multiple levels (e.g., Level 1 support, Level 2 support) until it is resolved.
*/
If you find my content valuable, please consider donating via Razorpay . You can add your name details in the note section on the payment page.
We are committed to helping individuals like you learn and navigate the tech stack in a better way. Your support enables us to continue providing high-quality learning resources.
If you have any additional topics—technical or non-technical—that you would like to explore, feel free to reach out to me at contact@niteshsynergy.com .