Design Principles in Software Development

ritiksharmaaaritiksharmaaa
28 min read

Design principles aim to provide solutions to recurring problems in software architecture. Below is a detailed article explaining common design patterns using real-world analogies, the problem they solve, and their implementation in both Python and JavaScript. this are some basic pattern there might be more but you can explore it in low-level-design .

Note : - After reading this article make sure to must read the next article which clarify Beginner developer to understand the practical implementation of this principal from the web developer perspective . also understand the role of DSA in web development or practical implementation in real life for absolute beginner to understand why DSA is important


1. Singleton Pattern

Problem:

Imagine a database connection pool. If multiple connections are created unnecessarily, it can lead to resource wastage. We need a way to ensure that only one instance of the database connection is created.

Solution:

The Singleton Pattern ensures a class has only one instance and provides a global point of access to it.

Example:

Analogy: A company can have only one CEO at a time. same as db also have to create on connection in entire code base so All queries go to the same object instance .

Python Code:

class DatabaseConnection:
    _instance = None

    def __new__(cls):
        if not cls._instance:
            cls._instance = super(DatabaseConnection, cls).__new__(cls)
        return cls._instance

# Usage no matter howm nay time you create instace of this object you 'll 
# get same object 
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2)  # True

JavaScript Code:

class DatabaseConnection {
    static instance = null;

    static getInstance() {
        if (!DatabaseConnection.instance) {
            DatabaseConnection.instance = new DatabaseConnection();
        }
        return DatabaseConnection.instance;
    }
}

// Usage
const db1 = DatabaseConnection.getInstance();
const db2 = DatabaseConnection.getInstance();
console.log(db1 === db2);  // True

2. Builder Pattern

Problem:

Constructing complex objects with numerous optional components can lead to messy code. We need a structured way to create such objects step by step.

Solution:

The Builder Pattern separates the construction of an object from its representation, allowing us to create different representations using the same construction process.

Example:

Analogy: Building a custom meal order at a restaurant.

Python Code:

class Meal:
    def __init__(self, builder):
        self.burger = builder.burger
        self.drink = builder.drink
        self.side = builder.side

    def __str__(self):
        return f"Meal: {self.burger} burger, {self.drink}, {self.side}"

class MealBuilder:
    def __init__(self):
        self.burger = "Classic"
        self.drink = "Cola"
        self.side = "Fries"

    def with_burger(self, burger):
        self.burger = burger
        return self

    def with_drink(self, drink):
        self.drink = drink
        return self

    def with_side(self, side):
        self.side = side
        return self

    def build(self):
        return Meal(self)

def main():
    # Create different meals using builder
    vegetarian_meal = (MealBuilder()
        .with_burger("Veggie")
        .with_drink("Lemonade")
        .with_side("Salad")
        .build()
    )

    classic_meal = (MealBuilder()
        .with_burger("Beef")
        .with_drink("Cola")
        .with_side("Fries")
        .build()
    )

    # Print meal details
    print(vegetarian_meal)
    print(classic_meal)

if __name__ == "__main__":
    main()

JavaScript Code:

class MealBuilder {
    constructor() {
        this.meal = {}; // or another class object you can use it 
    }

    addBurger(type) {
        this.meal.burger = type;
        return this;
    }

    addDrink(type) {
        this.meal.drink = type;
        return this;
    }

    build() {
        return this.meal;
    }
}

// Usage
const builder = new MealBuilder();
const meal = builder.addBurger("Veggie Burger").addDrink("Coke").build();
console.log(meal);

3. Bridge Pattern

Problem:

When a system has multiple independent dimensions of variability, adding or modifying features can lead to exponential growth in code complexity.

see Traditional approach

// Traditional Approach: Inheritance-Based Implementation

// Shapes with Direct Renderer Integration
class CircleVectorRenderer {
    render(radius) {
        console.log(`Drawing a VECTOR circle with radius ${radius}`);
    }
}

class CircleRasterRenderer {
    render(radius) {
        console.log(`Drawing a RASTER circle with radius ${radius}`);
    }
}

class SquareVectorRenderer {
    render(side) {
        console.log(`Drawing a VECTOR square with side ${side}`);
    }
}

class SquareRasterRenderer {
    render(side) {
        console.log(`Drawing a RASTER square with side ${side}`);
    }
}

// Shape Classes Directly Tied to Specific Renderers
class VectorCircle {
    constructor(radius) {
        this.radius = radius;
        this.renderer = new CircleVectorRenderer();
    }

    draw() {
        this.renderer.render(this.radius);
    }

    resize(factor) {
        this.radius *= factor;
    }
}

class RasterCircle {
    constructor(radius) {
        this.radius = radius;
        this.renderer = new CircleRasterRenderer();
    }

    draw() {
        this.renderer.render(this.radius);
    }

    resize(factor) {
        this.radius *= factor;
    }
}

class VectorSquare {
    constructor(side) {
        this.side = side;
        this.renderer = new SquareVectorRenderer();
    }

    draw() {
        this.renderer.render(this.side);
    }

    resize(factor) {
        this.side *= factor;
    }
}

class RasterSquare {
    constructor(side) {
        this.side = side;
        this.renderer = new SquareRasterRenderer();
    }

    draw() {
        this.renderer.render(this.side);
    }

    resize(factor) {
        this.side *= factor;
    }
}

// Traditional Device Control Example
class TVRemote {
    constructor() {
        this.tv = {
            isOn: false,
            volume: 50
        };
    }

    turnOn() {
        this.tv.isOn = true;
        console.log("TV turned on");
    }

    turnOff() {
        this.tv.isOn = false;
        console.log("TV turned off");
    }

    volumeUp() {
        if (this.tv.volume < 100) {
            this.tv.volume += 10;
            console.log(`TV volume increased to ${this.tv.volume}`);
        }
    }

    volumeDown() {
        if (this.tv.volume > 0) {
            this.tv.volume -= 10;
            console.log(`TV volume decreased to ${this.tv.volume}`);
        }
    }
}

class RadioRemote {
    constructor() {
        this.radio = {
            isOn: false,
            volume: 30
        };
    }

    turnOn() {
        this.radio.isOn = true;
        console.log("Radio turned on");
    }

    turnOff() {
        this.radio.isOn = false;
        console.log("Radio turned off");
    }

    volumeUp() {
        if (this.radio.volume < 100) {
            this.radio.volume += 10;
            console.log(`Radio volume increased to ${this.radio.volume}`);
        }
    }

    volumeDown() {
        if (this.radio.volume > 0) {
            this.radio.volume -= 10;
            console.log(`Radio volume decreased to ${this.radio.volume}`);
        }
    }
}

// Demonstration
function demonstrateTraditionalApproach() {
    console.log("--- Traditional Shape Rendering ---");

    // Multiple classes for each combination
    const vectorCircle = new VectorCircle(5);
    const rasterCircle = new RasterCircle(5);

    const vectorSquare = new VectorSquare(4);
    const rasterSquare = new RasterSquare(4);

    vectorCircle.draw();
    rasterCircle.draw();

    vectorSquare.draw();
    rasterSquare.draw();

    console.log("\n--- Traditional Device Control ---");

    const tvRemote = new TVRemote();
    const radioRemote = new RadioRemote();

    tvRemote.turnOn();
    tvRemote.volumeUp();

    radioRemote.turnOn();
    radioRemote.volumeDown();
}

// Run demonstration
demonstrateTraditionalApproach();

Solution:

The Bridge Pattern decouples an abstraction from its implementation, allowing the two to evolve independently.

Example:

Analogy: A remote control works with different devices (TV, radio) without needing separate remotes for each.

Python Code:

from abc import ABC, abstractmethod

# Implementation Hierarchy (Renderer)
class Renderer(ABC):
    @abstractmethod
    def render_circle(self, radius):
        pass

class VectorRenderer(Renderer):
    def render_circle(self, radius):
        print(f"Drawing a vector circle with radius {radius}")

class RasterRenderer(Renderer):
    def render_circle(self, radius):
        print(f"Drawing a raster circle with radius {radius}")

# Abstraction Hierarchy (Shape)
class Shape:
    def __init__(self, renderer):
        self.renderer = renderer

    @abstractmethod
    def draw(self):
        pass

    @abstractmethod
    def resize(self, factor):
        pass

class Circle(Shape):
    def __init__(self, renderer, radius):
        super().__init__(renderer)
        self.radius = radius

    def draw(self):
        self.renderer.render_circle(self.radius)

    def resize(self, factor):
        self.radius *= factor

# Create shapes with different renderers
vector_circle = Circle(VectorRenderer(), 5)
raster_circle = Circle(RasterRenderer(), 5)

vector_circle.draw()  # Draws vector circle
raster_circle.draw()  # Draws raster circle

# Can easily resize
vector_circle.resize(2)
vector_circle.draw()  # Draws resized vector circle

JavaScript Code:

// Implementation Hierarchy (Renderers)
class Renderer {
    renderCircle(radius) {
        throw new Error('Method must be implemented');
    }

    renderSquare(side) {
        throw new Error('Method must be implemented');
    }
}

class VectorRenderer extends Renderer {
    renderCircle(radius) {
        console.log(`Drawing a VECTOR circle with radius ${radius}`);
    }

    renderSquare(side) {
        console.log(`Drawing a VECTOR square with side ${side}`);
    }
}

class RasterRenderer extends Renderer {
    renderCircle(radius) {
        console.log(`Drawing a RASTER circle with radius ${radius}`);
    }

    renderSquare(side) {
        console.log(`Drawing a RASTER square with side ${side}`);
    }
}

// Abstraction Hierarchy (Shapes)
class Shape {
    constructor(renderer) {
        this.renderer = renderer;
    }

    // This method will delegate to the specific renderer
    render() {
        throw new Error('Method must be implemented');
    }
}

class Circle extends Shape {
    constructor(renderer, radius) {
        super(renderer);
        this.radius = radius;
    }

    render() {
        this.renderer.renderCircle(this.radius);
    }

    scale(factor) {
        this.radius *= factor;
    }
}

class Square extends Shape {
    constructor(renderer, side) {
        super(renderer);
        this.side = side;
    }

    render() {
        this.renderer.renderSquare(this.side);
    }

    scale(factor) {
        this.side *= factor;
    }
}

// Advanced Example: Device and Remote Bridge
class Device {
    isEnabled() { throw new Error('Must implement'); }
    enable() { throw new Error('Must implement'); }
    disable() { throw new Error('Must implement'); }
    getVolume() { throw new Error('Must implement'); }
    setVolume(percent) { throw new Error('Must implement'); }
}

class TV extends Device {
    constructor() {
        super();
        this._isOn = false;
        this._volume = 50;
    }

    isEnabled() { return this._isOn; }
    enable() { this._isOn = true; console.log('TV is ON'); }
    disable() { this._isOn = false; console.log('TV is OFF'); }
    getVolume() { return this._volume; }
    setVolume(percent) { 
        this._volume = Math.max(0, Math.min(100, percent));
        console.log(`TV Volume set to ${this._volume}`);
    }
}

class Radio extends Device {
    constructor() {
        super();
        this._isOn = false;
        this._volume = 30;
    }

    isEnabled() { return this._isOn; }
    enable() { this._isOn = true; console.log('Radio is ON'); }
    disable() { this._isOn = false; console.log('Radio is OFF'); }
    getVolume() { return this._volume; }
    setVolume(percent) { 
        this._volume = Math.max(0, Math.min(100, percent));
        console.log(`Radio Volume set to ${this._volume}`);
    }
}

class RemoteControl {
    constructor(device) {
        this.device = device;
    }

    togglePower() {
        if (this.device.isEnabled()) {
            this.device.disable();
        } else {
            this.device.enable();
        }
    }

    volumeUp() {
        const currentVolume = this.device.getVolume();
        this.device.setVolume(currentVolume + 10);
    }

    volumeDown() {
        const currentVolume = this.device.getVolume();
        this.device.setVolume(currentVolume - 10);
    }
}

class AdvancedRemoteControl extends RemoteControl {
    mute() {
        this.device.setVolume(0);
    }
}

// Usage Demonstration
function demonstrateShapeBridge() {
    console.log("--- Shape Bridge Demonstration ---");

    const vectorCircle = new Circle(new VectorRenderer(), 5);
    const rasterCircle = new Circle(new RasterRenderer(), 5);

    const vectorSquare = new Square(new VectorRenderer(), 4);
    const rasterSquare = new Square(new RasterRenderer(), 4);

    vectorCircle.render();   // Vector circle rendering
    rasterCircle.render();   // Raster circle rendering

    vectorSquare.render();   // Vector square rendering
    rasterSquare.render();   // Raster square rendering
}

function demonstrateRemoteBridge() {
    console.log("\n--- Remote Control Bridge Demonstration ---");

    const tv = new TV();
    const radio = new Radio();

    const basicTVRemote = new RemoteControl(tv);
    const advancedRadioRemote = new AdvancedRemoteControl(radio);

    basicTVRemote.togglePower();  // Turn on TV
    basicTVRemote.volumeUp();     // Increase TV volume

    advancedRadioRemote.togglePower(); // Turn on Radio
    advancedRadioRemote.volumeUp();    // Increase Radio volume
    advancedRadioRemote.mute();        // Mute Radio
}

// Run demonstrations
demonstrateShapeBridge();
demonstrateRemoteBridge();

4. Command Pattern

Problem:

We need a way to encapsulate requests (e.g., undoable actions) as objects, so they can be queued, logged, or executed later.

Traditional approach

class Light:
    def turn_on(self):
        print("Light is turned on")

    def turn_off(self):
        print("Light is turned off")

class Fan:
    def start(self):
        print("Fan is started")

    def stop(self):
        print("Fan is stopped")

# Without Command Pattern, we have tight coupling and no flexibility
class RemoteControl:
    def __init__(self):
        self.light = Light()
        self.fan = Fan()

    def press_light_on(self):
        self.light.turn_on()

    def press_light_off(self):
        self.light.turn_off()

    def press_fan_start(self):
        self.fan.start()

    def press_fan_stop(self):
        self.fan.stop()

# Usage
remote = RemoteControl()
remote.press_light_on()
remote.press_fan_start()

Solution:

The Command Pattern encapsulates a request as an object, separating the responsibility of issuing the request from handling it.

Example:

Analogy: A text editor allows undo/redo of actions like typing or formatting.

Python Code:

from abc import ABC, abstractmethod

# Command interface
class Command(ABC):
    @abstractmethod
    def execute(self):
        pass

    @abstractmethod
    def undo(self):
        pass

# Concrete Commands
class LightOnCommand(Command):
    def __init__(self, light):
        self.light = light

    def execute(self):
        self.light.turn_on()

    def undo(self):
        self.light.turn_off()

class LightOffCommand(Command):
    def __init__(self, light):
        self.light = light

    def execute(self):
        self.light.turn_off()

    def undo(self):
        self.light.turn_on()

class FanStartCommand(Command):
    def __init__(self, fan):
        self.fan = fan

    def execute(self):
        self.fan.start()

    def undo(self):
        self.fan.stop()

# Receiver classes (unchanged)
class Light:
    def turn_on(self):
        print("Light is turned on")

    def turn_off(self):
        print("Light is turned off")

class Fan:
    def start(self):
        print("Fan is started")

    def stop(self):
        print("Fan is stopped")

# Invoker
class RemoteControl:
    def __init__(self):
        self.command = None
        self.previous_command = None

    def set_command(self, command):
        self.previous_command = self.command
        self.command = command

    def press_button(self):
        if self.command:
            self.command.execute()

    def undo_last_command(self):
        if self.previous_command:
            self.previous_command.undo()

# Usage
def main():
    # Create receivers
    light = Light()
    fan = Fan()

    # Create commands
    light_on = LightOnCommand(light)
    light_off = LightOffCommand(light)
    fan_start = FanStartCommand(fan)

    # Create remote and execute commands
    remote = RemoteControl()

    remote.set_command(light_on)
    remote.press_button()  # Turns light on

    remote.set_command(fan_start)
    remote.press_button()  # Starts fan

    # Undo last command
    remote.undo_last_command()  # Stops fan

main()

JavaScript Code:

// Receiver classes
class Light {
    turnOn() {
        console.log("Light is turned on");
    }

    turnOff() {
        console.log("Light is turned off");
    }
}

class AudioSystem {
    turnUp() {
        console.log("Volume increased");
    }

    turnDown() {
        console.log("Volume decreased");
    }
}

// Command Interface
class Command {
    execute() {
        throw new Error("Abstract method must be implemented");
    }

    undo() {
        throw new Error("Abstract method must be implemented");
    }
}

// Concrete Command Classes
class LightOnCommand extends Command {
    constructor(light) {
        super();
        this.light = light;
    }

    execute() {
        this.light.turnOn();
    }

    undo() {
        this.light.turnOff();
    }
}

class LightOffCommand extends Command {
    constructor(light) {
        super();
        this.light = light;
    }

    execute() {
        this.light.turnOff();
    }

    undo() {
        this.light.turnOn();
    }
}

class VolumeUpCommand extends Command {
    constructor(audioSystem) {
        super();
        this.audioSystem = audioSystem;
    }

    execute() {
        this.audioSystem.turnUp();
    }

    undo() {
        this.audioSystem.turnDown();
    }
}

// Macro Command (Complex Command)
class MacroCommand extends Command {
    constructor(commands) {
        super();
        this.commands = commands;
    }

    execute() {
        this.commands.forEach(command => command.execute());
    }

    undo() {
        // Undo in reverse order
        [...this.commands].reverse().forEach(command => command.undo());
    }
}

// Invoker (Remote Control)
class RemoteControl {
    constructor() {
        this.history = [];
    }

    submitCommand(command) {
        command.execute();
        this.history.push(command);
    }

    undoLastCommand() {
        if (this.history.length > 0) {
            const lastCommand = this.history.pop();
            lastCommand.undo();
        }
    }

    // Undo multiple commands
    undoMultipleCommands(count) {
        for (let i = 0; i < count && this.history.length > 0; i++) {
            const lastCommand = this.history.pop();
            lastCommand.undo();
        }
    }
}

// Smart Home Demonstration
function smartHomeDemo() {
    // Create receivers
    const livingRoomLight = new Light();
    const audioSystem = new AudioSystem();

    // Create individual commands
    const lightOnCommand = new LightOnCommand(livingRoomLight);
    const lightOffCommand = new LightOffCommand(livingRoomLight);
    const volumeUpCommand = new VolumeUpCommand(audioSystem);

    // Create macro command
    const partyModeCommands = [
        lightOnCommand,
        volumeUpCommand
    ];
    const partyModeCommand = new MacroCommand(partyModeCommands);

    // Create remote control
    const remote = new RemoteControl();

    // Demonstrate command usage
    console.log("--- Executing Individual Commands ---");
    remote.submitCommand(lightOnCommand);  // Turn on light
    remote.submitCommand(volumeUpCommand);  // Increase volume

    console.log("\n--- Undo Last Command ---");
    remote.undoLastCommand();  // Decrease volume

    console.log("\n--- Party Mode Macro Command ---");
    remote.submitCommand(partyModeCommand);  // Execute multiple commands at once

    console.log("\n--- Undo Party Mode ---");
    remote.undoLastCommand();  // Undo entire macro command
}

// Run the demonstration
smartHomeDemo();

5. Factory Pattern

Problem:

Creating objects directly using new ties the code to specific classes, making it hard to switch implementations.

Problem Scenario: Let's consider a scenario where we're building a logistics management system that needs to handle different types of transportation.

Initial Problem Code:

pythonCopyclass Truck:
    def deliver(self):
        return "Delivering by truck"

class Ship:
    def deliver(self):
        return "Delivering by ship"

class Logistics:
    def __init__(self, transport_type):
        self.transport_type = transport_type

        if transport_type == "truck":
            self.transport = Truck()
        elif transport_type == "ship":
            self.transport = Ship()
        else:
            raise ValueError("Unknown transport type")

    def plan_delivery(self):
        return self.transport.deliver()

# Usage
logistics1 = Logistics("truck")
print(logistics1.plan_delivery())  # Delivering by truck

logistics2 = Logistics("ship")
print(logistics2.plan_delivery())  # Delivering by ship

Problems with this approach:

  1. The Logistics class has direct dependencies on concrete classes

  2. Adding a new transport type requires modifying the existing class

  3. The creation logic is tightly coupled with the Logistics class

Solution with Factory Pattern:

pythonCopyfrom abc import ABC, abstractmethod

# Abstract Product
class Transport(ABC):
    @abstractmethod
    def deliver(self):
        pass

# Concrete Products
class Truck(Transport):
    def deliver(self):
        return "Delivering by truck"

class Ship(Transport):
    def deliver(self):
        return "Delivering by ship"

class Airplane(Transport):
    def deliver(self):
        return "Delivering by airplane"

# Factory Abstract Class
class LogisticsFactory(ABC):
    @abstractmethod
    def create_transport(self):
        pass

    def plan_delivery(self):
        transport = self.create_transport()
        return transport.deliver()

# Concrete Factories
class TruckLogistics(LogisticsFactory):
    def create_transport(self):
        return Truck()

class ShipLogistics(LogisticsFactory):
    def create_transport(self):
        return Ship()

class AirplaneLogistics(LogisticsFactory):
    def create_transport(self):
        return Airplane()

# Usage
def client_code(logistics_factory):
    print(logistics_factory.plan_delivery())

# Demonstrate usage
client_code(TruckLogistics())    # Delivering by truck
client_code(ShipLogistics())     # Delivering by ship
client_code(Airplan

JavaScript Code:

// Abstract Product (Interface-like definition)
class Transport {
    deliver() {
        throw new Error("Method 'deliver()' must be implemented");
    }
}

// Concrete Products
class Truck extends Transport {
    deliver() {
        return "Delivering by truck";
    }
}

class Ship extends Transport {
    deliver() {
        return "Delivering by ship";
    }
}

class Airplane extends Transport {
    deliver() {
        return "Delivering by airplane";
    }
}

// Factory Method Pattern
class LogisticsFactory {
    // Static factory method
    static createTransport(type) {
        const transportMap = {
            truck: () => new Truck(),
            ship: () => new Ship(),
            airplane: () => new Airplane()
        };

        const transportCreator = transportMap[type];

        if (!transportCreator) {
            throw new Error(`Unknown transport type: ${type}`);
        }

        return transportCreator();
    }

    // Alternative: Class-based factory method
    createTransport() {
        throw new Error("Subclass must implement abstract method");
    }

    planDelivery() {
        const transport = this.createTransport();
        return transport.deliver();
    }
}

// Concrete Factories
class TruckLogistics extends LogisticsFactory {
    createTransport() {
        return new Truck();
    }
}

class ShipLogistics extends LogisticsFactory {
    createTransport() {
        return new Ship();
    }
}

class AirplaneLogistics extends LogisticsFactory {
    createTransport() {
        return new Airplane();
    }
}

// Abstract Factory Pattern Example
class TransportFactory {
    constructor(logisticsType) {
        this.logistics = this.createLogistics(logisticsType);
    }

    createLogistics(type) {
        const logisticsMap = {
            truck: TruckLogistics,
            ship: ShipLogistics,
            airplane: AirplaneLogistics
        };

        const LogisticsClass = logisticsMap[type];

        if (!LogisticsClass) {
            throw new Error(`Unsupported logistics type: ${type}`);
        }

        return new LogisticsClass();
    }

    planDelivery() {
        return this.logistics.planDelivery();
    }
}

// Demonstration
function demonstrateFactoryPatterns() {
    console.log("--- Static Factory Method ---");
    const truck1 = LogisticsFactory.createTransport('truck');
    console.log(truck1.deliver());

    console.log("\n--- Inheritance-based Factory Method ---");
    const truckLogistics = new TruckLogistics();
    console.log(truckLogistics.planDelivery());

    console.log("\n--- Abstract Factory Pattern ---");
    const transportFactory1 = new TransportFactory('ship');
    console.log(transportFactory1.planDelivery());

    // Error handling demonstration
    try {
        LogisticsFactory.createTransport('rocket');
    } catch (error) {
        console.log("\n--- Error Handling ---");
        console.log(error.message);
    }
}

// Run the demonstration
demonstrateFactoryPatterns();

// Export for potential module usage
export { 
    Transport, 
    Truck, 
    Ship, 
    Airplane, 
    LogisticsFactory, 
    TransportFactory 
};

6. Adapter Pattern

Problem:

Incompatible interfaces between two systems make it difficult to integrate them. For example, a new payment gateway API might not match the current system's structure.

Problem Explained

In the problem code:

  1. We have a LegacyRectangle class with a legacy_draw() method that takes specific coordinate parameters.

  2. The new system expects a Shape interface with a draw() method that takes start and end points.

  3. These interfaces are incompatible, making direct use impossible.

Adapter Solution

The RectangleAdapter solves this by:

  1. Implementing the new Shape interface

  2. Wrapping the legacy LegacyRectangle

  3. Translating the new method call to the legacy method call

  4. Allowing seamless integration of the old code with the ne

python Code :

# Problem Code: Incompatible Interfaces

# Legacy System: Rectangle Renderer
class LegacyRectangle:
    def __init__(self, x1, y1, x2, y2):
        self.x1 = x1
        self.y1 = y1
        self.x2 = x2
        self.y2 = y2

    def legacy_draw(self):
        print(f"Drawing rectangle from ({self.x1},{self.y1}) to ({self.x2},{self.y2})")

# New System: Shape Interface Expecting Different Method Signature
class Shape:
    def draw(self, start_point, end_point):
        pass

# Problem: Direct use is not possible
class Client:
    def render_shape(self, shape):
        # This won't work with LegacyRectangle
        shape.draw((0, 0), (100, 100))

# Adapter Solution
class RectangleAdapter(Shape):
    def __init__(self, legacy_rectangle):
        self.legacy_rectangle = legacy_rectangle

    def draw(self, start_point, end_point):
        # Translate new interface call to legacy method
        x1, y1 = start_point
        x2, y2 = end_point

        # Create a new LegacyRectangle with adapted coordinates
        adapted_rectangle = LegacyRectangle(x1, y1, x2, y2)

        # Call the legacy method
        adapted_rectangle.legacy_draw()

# Demonstration
def main():
    # Legacy rectangle with old-style coordinates
    legacy_rect = LegacyRectangle(10, 20, 50, 60)

    # Adapt the legacy rectangle to the new interface
    adapter_rect = RectangleAdapter(legacy_rect)

    # Now we can use the legacy rectangle through the new interface
    client = Client()
    client.render_shape(adapter_rect)

if __name__ == "__main__":
    main()

JavaScript Code:

// Legacy System: Rectangle Renderer
class LegacyRectangle {
    constructor(x1, y1, x2, y2) {
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
    }

    // Legacy drawing method
    legacyDraw() {
        console.log(`Drawing rectangle from (${this.x1},${this.y1}) to (${this.x2},${this.y2})`);
    }
}

// New System: Shape Interface
class Shape {
    // Abstract method to be implemented by adapters
    draw(startPoint, endPoint) {
        throw new Error('Must implement draw method');
    }
}

// Client Class
class Client {
    // Expects a shape with draw method
    renderShape(shape) {
        // Calls draw method with specific coordinates
        shape.draw([0, 0], [100, 100]);
    }
}

// Adapter
class RectangleAdapter extends Shape {
    constructor(legacyRectangle) {
        super();
        this.legacyRectangle = legacyRectangle;
    }

    // Adapt the new interface to the legacy method
    draw(startPoint, endPoint) {
        const [x1, y1] = startPoint;
        const [x2, y2] = endPoint;

        // Create a new LegacyRectangle with adapted coordinates
        const adaptedRectangle = new LegacyRectangle(x1, y1, x2, y2);

        // Call the legacy drawing method
        adaptedRectangle.legacyDraw();
    }
}

// Demonstration Function
function demonstrateAdapterPattern() {
    // Create a legacy rectangle
    const legacyRect = new LegacyRectangle(10, 20, 50, 60);

    // Adapt the legacy rectangle to the new interface
    const adaptedRect = new RectangleAdapter(legacyRect);

    // Create client and render the shape
    const client = new Client();
    client.renderShape(adaptedRect);

    // Another example with different coordinates
    const anotherLegacyRect = new LegacyRectangle(5, 10, 75, 90);
    const anotherAdaptedRect = new RectangleAdapter(anotherLegacyRect);
    client.renderShape(anotherAdaptedRect);
}

// Run the demonstration
demonstrateAdapterPattern();

7. Proxy Pattern

Problem:

Accessing a resource-heavy object directly can be costly. For example, loading large images can slow down the application.

Problem Code (Without Proxy):

pythonCopyclass RealBankAccount:
    def __init__(self, balance):
        self._balance = balance

    def withdraw(self, amount):
        if amount > self._balance:
            print("Insufficient funds")
            return False
        self._balance -= amount
        print(f"Withdrew {amount}. Remaining balance: {self._balance}")
        return True

# Direct usage
account = RealBankAccount(1000)
account.withdraw(1500)  # This allows direct access with no additional checks

Issues with the problem code:

  1. No access control or additional security

  2. No logging of transactions

  3. Direct manipulation of the account is possible

  4. Lacks additional validation or business logic

Solution Code (With Proxy):

pythonCopyclass RealBankAccount:
    def __init__(self, balance):
        self._balance = balance

    def get_balance(self):
        return self._balance

    def withdraw(self, amount):
        self._balance -= amount

class BankAccountProxy:
    def __init__(self, real_account, user_role):
        self._real_account = real_account
        self._user_role = user_role
        self._transaction_log = []

    def withdraw(self, amount):
        # Access control
        if self._user_role != 'admin' and amount > 500:
            print("Unauthorized withdrawal amount")
            return False

        # Balance check
        if amount > self._real_account.get_balance():
            print("Insufficient funds")
            return False

        # Logging
        self._log_transaction(amount)

        # Delegate to real account
        self._real_account.withdraw(amount)
        print(f"Withdrew {amount}. Remaining balance: {self._real_account.get_balance()}")
        return True

    def _log_transaction(self, amount):
        self._transaction_log.append({
            'amount': amount,
            'timestamp': __import__('datetime').datetime.now()
        })

    def get_transaction_log(self):
        return self._transaction_log

# Usage
real_account = RealBankAccount(1000)
admin_proxy = BankAccountProxy(real_account, 'admin')
user_proxy = BankAccountProxy(real_account, 'user')

# Admin can withdraw large amounts
admin_proxy.withdraw(700)  # Successful
user_proxy.withdraw(700)   # Unauthorized

Key Proxy Pattern Characteristics Demonstrated:

  1. Protection Proxy:

    • Adds access control based on user roles

    • Controls withdrawal limits

    • Prevents unauthorized actions

  2. Logging Proxy:

    • Maintains a transaction log

    • Tracks all withdrawal attempts

  3. Control Flow:

    • Validates conditions before delegating to the real object

    • Adds additional business logic

    • Provides an extra layer of security and functionality

Benefits of the Proxy Pattern:

  • Separates concerns

  • Adds additional behavior without modifying the original class

  • Provides a controlled access point

  • Enables additional functionality like logging, caching, or access control

  • Maintains the original interface of the real object

Common Proxy Types:

  1. Remote Proxy: Represents an object located in a different system

  2. Virtual Proxy: Provides placeholder functionality until the real object is needed

  3. Protection Proxy: Controls access to the original object

  4. Caching Proxy: Stores and returns cached results

  5. Logging Proxy: Tracks method calls and their parameters

When to Use:

  • When you need to control access to an object

  • When you want to add additional behaviors without changing the original class

  • When you need to lazy-load expensive resources

  • When you want to implement distributed system communication

The Proxy pattern provides a way to add an extra layer of control, logging, or functionality to an existing class without modifying its original implementation, making it a powerful tool for enhancing object interactions.

CopyRetry

Claude does not have the ability to run the code it generates yet.

JavaScript Code:

// Basic Protection Proxy for Bank Account
class RealBankAccount {
    constructor(balance) {
        this._balance = balance;
    }

    getBalance() {
        return this._balance;
    }

    withdraw(amount) {
        this._balance -= amount;
        return true;
    }
}

class BankAccountProxy {
    constructor(account, userRole) {
        this._account = account;
        this._userRole = userRole;
        this._transactionLog = [];
    }

    withdraw(amount) {
        // Protection mechanisms
        if (this._userRole !== 'admin' && amount > 500) {
            console.log('Withdrawal limit exceeded');
            return false;
        }

        if (amount > this._account.getBalance()) {
            console.log('Insufficient funds');
            return false;
        }

        // Logging
        this._logTransaction(amount);

        // Delegate to real account
        return this._account.withdraw(amount);
    }

    _logTransaction(amount) {
        this._transactionLog.push({
            amount,
            timestamp: new Date(),
            role: this._userRole
        });
    }

    getTransactionLog() {
        return this._transactionLog;
    }
}

// Virtual Proxy (Lazy Loading Example)
class ExpensiveDataLoader {
    constructor() {
        this._data = null;
    }

    // Simulates expensive data loading
    loadData() {
        if (!this._data) {
            console.log('Loading expensive data...');
            this._data = {
                users: Array(1000).fill().map((_, i) => `User ${i+1}`),
                timestamp: Date.now()
            };
        }
        return this._data;
    }
}

class VirtualProxyDataLoader {
    constructor() {
        this._loader = null;
    }

    getData() {
        if (!this._loader) {
            this._loader = new ExpensiveDataLoader();
        }
        return this._loader.loadData();
    }
}

// Proxy Using ES6 Proxy Object
class SensitiveDataService {
    constructor() {
        this._sensitiveData = {
            creditCard: '1234-5678-9012-3456',
            ssn: '123-45-6789'
        };
    }

    getSensitiveData() {
        return this._sensitiveData;
    }
}

function createSecureProxy(service) {
    return new Proxy(service, {
        get(target, prop) {
            // Check if user is authorized
            const isAuthorized = checkAuthorization();

            if (isAuthorized) {
                console.log(`Accessing ${prop}`);
                return target[prop];
            } else {
                console.error('Unauthorized access');
                return null;
            }
        }
    });
}

// Mock authorization check
function checkAuthorization() {
    // In real-world, this would be a more complex authentication mechanism
    const userRole = 'admin';
    return userRole === 'admin';
}

// Demonstration
function demonstrateProxies() {
    // Bank Account Proxy
    console.log('--- Bank Account Proxy ---');
    const realAccount = new RealBankAccount(1000);
    const adminProxy = new BankAccountProxy(realAccount, 'admin');
    const userProxy = new BankAccountProxy(realAccount, 'user');

    adminProxy.withdraw(700);  // Successful
    userProxy.withdraw(700);   // Blocked

    // Virtual Proxy
    console.log('\n--- Virtual Proxy (Lazy Loading) ---');
    const virtualLoader = new VirtualProxyDataLoader();
    console.log(virtualLoader.getData());  // First call loads data
    console.log(virtualLoader.getData());  // Second call uses cached data

    // ES6 Proxy for Access Control
    console.log('\n--- ES6 Proxy for Access Control ---');
    const sensitiveService = new SensitiveDataService();
    const secureProxy = createSecureProxy(sensitiveService);

    // This will work if authorized
    const sensitiveData = secureProxy.getSensitiveData();
    console.log(sensitiveData);
}

// Run the demonstration
demonstrateProxies();

8. Observer Pattern

Problem:

In a one-to-many relationship, multiple objects need to be notified when a state changes. For example, a news publisher must notify subscribers about new articles.

Problem Code (Tightly Coupled):

pythonCopyclass NewsPublisher:
    def __init__(self):
        self.subscribers = []
        self.latest_news = None

    def publish_news(self, news):
        self.latest_news = news
        for subscriber in self.subscribers:
            if subscriber == "EmailSubscriber":
                print(f"Sending email with news: {news}")
            elif subscriber == "SMSSubscriber":
                print(f"Sending SMS with news: {news}")
            elif subscriber == "MobileAppSubscriber":
                print(f"Sending mobile app notification with news: {news}")

In this problematic design:

  • The publisher knows too much about its subscribers

  • Adding a new type of subscriber requires modifying the existing code

  • The code violates the Open/Closed Principle

  • It's not flexible or extensible

# Define the abstract base class for observers
from abc import ABC, abstractmethod

class Observer(ABC):
    @abstractmethod
    def update(self, news):
        """Receive updates from the subject"""
        pass

# Define the subject (publisher) interface
class Subject:
    def __init__(self):
        self._observers = []

    def attach(self, observer):
        """Add an observer to the list of subscribers"""
        if observer not in self._observers:
            self._observers.append(observer)

    def detach(self, observer):
        """Remove an observer from the list of subscribers"""
        self._observers.remove(observer)

    def notify(self, news):
        """Notify all observers with the latest news"""
        for observer in self._observers:
            observer.update(news)

# Concrete implementation of the news publisher
class NewsPublisher(Subject):
    def __init__(self):
        super().__init__()
        self._latest_news = None

    def publish_news(self, news):
        """Publish news and notify all subscribers"""
        self._latest_news = news
        self.notify(news)

    @property
    def latest_news(self):
        return self._latest_news

# Concrete observer implementations
class EmailSubscriber(Observer):
    def update(self, news):
        print(f"Email Subscriber: Received news - {news}")

class SMSSubscriber(Observer):
    def update(self, news):
        print(f"SMS Subscriber: Received news - {news}")

class MobileAppSubscriber(Observer):
    def update(self, news):
        print(f"Mobile App Subscriber: Received news - {news}")

# Optional: Monitor subscriber
class LoggingSubscriber(Observer):
    def update(self, news):
        print(f"Logging: News received - {news}")

# Demonstration
def main():
    # Create the publisher
    news_publisher = NewsPublisher()

    # Create subscribers
    email_sub = EmailSubscriber()
    sms_sub = SMSSubscriber()
    mobile_sub = MobileAppSubscriber()
    logging_sub = LoggingSubscriber()

    # Subscribe to the publisher
    news_publisher.attach(email_sub)
    news_publisher.attach(sms_sub)
    news_publisher.attach(mobile_sub)
    news_publisher.attach(logging_sub)

    # Publish some news
    news_publisher.publish_news("Breaking: Python 3.12 Released!")

    print("\n--- Removing SMS Subscriber ---\n")

    # Unsubscribe a subscriber
    news_publisher.detach(sms_sub)

    # Publish more news
    news_publisher.publish_news("Tech Conference Announced!")

# Run the demonstration
if __name__ == "__main__":
    main()

Observer Pattern Implementation

Key Improvements in the Observer Pattern Solution:

  1. Decoupling:

    • The publisher (Subject) doesn't know the specific details of its subscribers

    • Each subscriber implements the Observer interface independently

  2. Flexibility:

    • Easy to add new types of subscribers without modifying existing code

    • Subscribers can be added or removed dynamically

  3. Design Principles Followed:

    • Open/Closed Principle: Open for extension, closed for modification

    • Dependency Inversion Principle: Depend on abstractions (Observer interface)

  4. Benefits:

    • Loose coupling between publisher and subscribers

    • Supports one-to-many relationships

    • Allows for dynamic subscription management

When you run this code, you'll see how different subscribers receive the same news update without the publisher needing to know their specific implementation details.

The key steps in implementing the Observer pattern are:

  1. Define an Observer interface

  2. Create a Subject (publisher) with attach, detach, and notify methods

  3. Implement concrete observers that follow the Observer interface

  4. Allow dynamic subscription and unsubscription

Would you like me to elaborate on any part of the Observer pattern implementation?

Solution:

The Observer Pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified.

JavaScript Code:

// Python-like Observer Pattern Implementation in JavaScript

// Base Classes Mimicking Python's Abstract Base Classes
class Observer {
    // Python's @abstractmethod equivalent
    update(data) {
        throw new Error("Abstract method must be implemented");
    }
}

class Subject {
    constructor() {
        this._observers = [];
    }

    // Equivalent to Python's attach method
    subscribe(observer) {
        if (!this._observers.includes(observer)) {
            this._observers.push(observer);
        }
    }

    // Equivalent to Python's detach method
    unsubscribe(observer) {
        this._observers = this._observers.filter(obs => obs !== observer);
    }

    // Equivalent to Python's notify method
    notify(data) {
        this._observers.forEach(observer => {
            observer.update(data);
        });
    }
}

// Concrete Implementation: Social Media Notification System
class SocialMediaPlatform extends Subject {
    constructor() {
        super();
        this._notifications = [];
    }

    createPost(user, content) {
        const notification = {
            user,
            content,
            timestamp: new Date()
        };

        this._notifications.push(notification);
        this.notify(notification);
    }

    get latestNotifications() {
        return this._notifications;
    }
}

// Concrete Observers
class EmailNotifier extends Observer {
    constructor(email) {
        super();
        this.email = email;
    }

    update(notification) {
        console.log(`
[Email Notification]
To: ${this.email}
User: ${notification.user}
Content: ${notification.content}
Timestamp: ${notification.timestamp.toLocaleString()}
        `);
    }
}

class MobileAppNotifier extends Observer {
    constructor(deviceId) {
        super();
        this.deviceId = deviceId;
    }

    update(notification) {
        console.log(`
[Mobile Push Notification]
Device: ${this.deviceId}
User: ${notification.user}
Content: ${notification.content}
Timestamp: ${notification.timestamp.toLocaleString()}
        `);
    }
}

class LoggingService extends Observer {
    update(notification) {
        console.log(`
[System Log]
LOG: New post by ${notification.user}
Full Content: ${notification.content}
Logged at: ${new Date().toLocaleString()}
        `);
    }
}

// Demonstration Function
function demonstrateSocialMediaObserver() {
    // Create the social media platform (Subject)
    const socialMedia = new SocialMediaPlatform();

    // Create Observers
    const emailNotifier1 = new EmailNotifier('user1@example.com');
    const emailNotifier2 = new EmailNotifier('admin@platform.com');
    const mobileNotifier = new MobileAppNotifier('device-123-abc');
    const loggingService = new LoggingService();

    // Subscribe observers
    socialMedia.subscribe(emailNotifier1);
    socialMedia.subscribe(emailNotifier2);
    socialMedia.subscribe(mobileNotifier);
    socialMedia.subscribe(loggingService);

    // Simulate social media posts
    console.log("\n--- First Post ---");
    socialMedia.createPost('alice', 'Hello, world! First post on the platform!');

    console.log("\n--- Removing Email Notifiers ---");
    // Unsubscribe some observers
    socialMedia.unsubscribe(emailNotifier1);
    socialMedia.unsubscribe(emailNotifier2);

    console.log("\n--- Second Post ---");
    socialMedia.createPost('bob', 'Just testing the platform features.');
}

// Run the demonstration
demonstrateSocialMediaObserver();

// Bonus: Functional Observer Pattern (More JavaScript-like)
function createSubject() {
    const observers = new Set();

    return {
        subscribe(observer) {
            observers.add(observer);
            return () => observers.delete(observer);
        },
        notify(data) {
            observers.forEach(observer => observer(data));
        }
    };
}

function demonstrateFunctionalObserver() {
    console.log("\n--- Functional Observer Demonstration ---");

    const eventBus = createSubject();

    // Functional observers
    const logObserver = (event) => {
        console.log(`Functional Log: ${JSON.stringify(event)}`);
    };

    const alertObserver = (event) => {
        console.log(`🚨 Alert: ${JSON.stringify(event)}`);
    };

    // Subscribe observers
    const unsubscribeLog = eventBus.subscribe(logObserver);
    const unsubscribeAlert = eventBus.subscribe(alertObserver);

    // Trigger notifications
    eventBus.notify({ type: 'user_login', user: 'john_doe' });

    // Unsubscribe
    unsubscribeLog();

    // This will only trigger alert observer
    eventBus.notify({ type: 'system_update', status: 'completed' });
}

// Uncomment to run functional observer demo
// demonstrateFunctionalObserver();

9. Iterator Pattern

Problem:

Accessing elements of a collection sequentially without exposing its underlying representation can be challenging.

Problem

Imagine you have a complex data structure like a custom collection (e.g., a tree, graph, or specialized list) and you want to provide a way to access its elements sequentially without exposing its underlying representation. Traditional approaches might:

  • Expose internal structure of the collection

  • Couple traversal logic with the collection's implementation

  • Make it difficult to create multiple traversal methods

  • Complicate the main collection class with traversal logic

Solution: Iterator Pattern

The Iterator Pattern solves these problems by:

  • Providing a way to access elements of a collection sequentially

  • Encapsulating the traversal mechanism

  • Allowing different traversal algorithms

  • Keeping the collection's implementation separate from how it's traversed

Here's a Python implementation demonstrating the Iterator Pattern:

pythonCopyfrom typing import List, Any

# Iterator Interface
class Iterator:
    def has_next(self) -> bool:
        pass

    def next(self) -> Any:
        pass

# Concrete Collection
class BookCollection:
    def __init__(self):
        self._books: List[str] = []

    def add_book(self, book: str):
        self._books.append(book)

    def create_iterator(self):
        return BookIterator(self._books)

# Concrete Iterator
class BookIterator(Iterator):
    def __init__(self, books: List[str]):
        self._books = books
        self._position = 0

    def has_next(self) -> bool:
        return self._position < len(self._books)

    def next(self) -> str:
        if not self.has_next():
            raise StopIteration("No more books")

        book = self._books[self._position]
        self._position += 1
        return book

# Client Code
def main():
    # Create a book collection
    book_collection = BookCollection()
    book_collection.add_book("Clean Code")
    book_collection.add_book("Design Patterns")
    book_collection.add_book("Refactoring")

    # Get iterator
    iterator = book_collection.create_iterator()

    # Traverse books
    print("Books in the collection:")
    while iterator.has_next():
        print(iterator.next())

# Alternative: Using Python's built-in iterator protocol
class AdvancedBookCollection:
    def __init__(self):
        self._books: List[str] = []

    def add_book(self, book: str):
        self._books.append(book)

    def __iter__(self):
        return iter(self._books)

def alternative_main():
    collection = AdvancedBookCollection()
    collection.add_book("Python Cookbook")
    collection.add_book("Effective Python")

    for book in collection:
        print(book)

if __name__ == "__main__":
    main()
    print("\n--- Alternative Implementation ---")
    alternative_main()

Key Components

  1. Iterator Interface: Defines methods for traversing elements

    • has_next(): Checks if more elements exist

    • next(): Retrieves the next element

  2. Concrete Iterator: Implements the traversal logic

    • Keeps track of the current position

    • Provides methods to access elements

  3. Collection: Provides a method to create an iterator

    • Allows decoupling of traversal from collection structure

Benefits

  • Separates the traversal algorithm from the collection

  • Supports different traversal methods

  • Simplifies the collection's interface

  • Allows lazy loading of elements

  • Provides a standard way to iterate through different types of collections

When to Use

  • When you want to access a collection's elements without exposing its internal structure

  • When you need multiple traversal methods for the same collection

  • When you want to provide a standard way of iterating through different types of collections

Practical Considerations

  1. Python has built-in iterator support through the __iter__() and __next__() methods

  2. Many languages provide native iterator interfaces

  3. Can be extended to support more complex traversal strategies

The example demonstrates two approaches:

  1. A custom iterator implementation

  2. Python's built-in iterator protocol

This pattern is particularly useful in scenarios with complex data structures or when you need flexible traversal mechanisms.

CopyRetry

Claude does not have the ability to run the code it generates yet.

JavaScript Code:

// Classic Iterator Pattern Implementation
class BookCollection {
    constructor() {
        this.books = [];
    }

    addBook(book) {
        this.books.push(book);
    }

    createIterator() {
        return new BookIterator(this.books);
    }
}

class BookIterator {
    constructor(books) {
        this.books = books;
        this.index = 0;
    }

    hasNext() {
        return this.index < this.books.length;
    }

    next() {
        return this.hasNext() ? this.books[this.index++] : null;
    }

    // Reset iterator
    reset() {
        this.index = 0;
    }
}

// Example Usage
function demonstrateClassicIterator() {
    console.log("--- Classic Iterator Example ---");
    const bookCollection = new BookCollection();
    bookCollection.addBook("JavaScript Design Patterns");
    bookCollection.addBook("Eloquent JavaScript");
    bookCollection.addBook("You Don't Know JS");

    const iterator = bookCollection.createIterator();
    while(iterator.hasNext()) {
        console.log(iterator.next());
    }
}

// ES6 Generator-based Iterator
class AdvancedBookCollection {
    constructor() {
        this.books = [];
    }

    addBook(book) {
        this.books.push(book);
    }

    // Generator method
    *[Symbol.iterator]() {
        for (let book of this.books) {
            yield book;
        }
    }

    // Custom generator with filtering
    *filteredBooks(filterFn) {
        for (let book of this.books) {
            if (filterFn(book)) {
                yield book;
            }
        }
    }
}

// Custom Iterator with More Advanced Functionality
function demonstrateGeneratorIterator() {
    console.log("\n--- Generator Iterator Example ---");
    const collection = new AdvancedBookCollection();
    collection.addBook("Clean Code");
    collection.addBook("Refactoring");
    collection.addBook("Design Patterns");

    // Using built-in iteration
    for (let book of collection) {
        console.log(book);
    }

    // Using custom filtered iterator
    console.log("\n--- Filtered Books ---");
    const filteredIterator = collection.filteredBooks(
        book => book.includes("Code")
    );
    for (let book of filteredIterator) {
        console.log(book);
    }
}

// Complex Iterator with Multiple Traversal Strategies
class ComplexCollection {
    constructor() {
        this.items = [];
    }

    addItem(item) {
        this.items.push(item);
    }

    // Forward iterator
    forwardIterator() {
        let index = 0;
        return {
            next: () => {
                return index < this.items.length 
                    ? { value: this.items[index++], done: false }
                    : { done: true };
            }
        };
    }

    // Reverse iterator
    reverseIterator() {
        let index = this.items.length - 1;
        return {
            next: () => {
                return index >= 0 
                    ? { value: this.items[index--], done: false }
                    : { done: true };
            }
        };
    }
}

function demonstrateComplexIterator() {
    console.log("\n--- Complex Iterator Example ---");
    const collection = new ComplexCollection();
    collection.addItem("First");
    collection.addItem("Second");
    collection.addItem("Third");

    console.log("Forward Iteration:");
    const forwardIter = collection.forwardIterator();
    let result = forwardIter.next();
    while (!result.done) {
        console.log(result.value);
        result = forwardIter.next();
    }

    console.log("\nReverse Iteration:");
    const reverseIter = collection.reverseIterator();
    result = reverseIter.next();
    while (!result.done) {
        console.log(result.value);
        result = reverseIter.next();
    }
}

// Infinite Iterator Example
class InfiniteSequenceIterator {
    constructor(generator) {
        this.generator = generator;
        this.current = generator();
    }

    next() {
        return this.current.next();
    }
}

function* fibonacciGenerator() {
    let [prev, curr] = [0, 1];
    while (true) {
        yield curr;
        [prev, curr] = [curr, prev + curr];
    }
}

function demonstrateInfiniteIterator() {
    console.log("\n--- Infinite Iterator Example ---");
    const fibIterator = new InfiniteSequenceIterator(fibonacciGenerator);

    // Get first 10 Fibonacci numbers
    for (let i = 0; i < 10; i++) {
        console.log(fibIterator.next().value);
    }
}

// Run all demonstrations
function main() {
    demonstrateClassicIterator();
    demonstrateGeneratorIterator();
    demonstrateComplexIterator();
    demonstrateInfiniteIterator();
}

main();

Each pattern tackles a specific type of problem, providing structured solutions to improve code reusability, scalability, and maintainability. Let me know if you'd like to explore more patterns or delve into other principles!

Conclusion

This pattern you don’t have to memorize it because this pattern is not for beginner but you have to atleast familiar with one time so next time when you have a situation you can implement it . or you have to use this patter in day to day life. once you familar may you are considered as a sde 2 role developer .

0
Subscribe to my newsletter

Read articles from ritiksharmaaa directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

ritiksharmaaa
ritiksharmaaa

Hy this is me Ritik sharma . i am software developer