Design Principles in Software Development


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:
The
Logistics
class has direct dependencies on concrete classesAdding a new transport type requires modifying the existing class
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:
We have a
LegacyRectangle
class with alegacy_draw()
method that takes specific coordinate parameters.The new system expects a
Shape
interface with adraw()
method that takes start and end points.These interfaces are incompatible, making direct use impossible.
Adapter Solution
The RectangleAdapter
solves this by:
Implementing the new
Shape
interfaceWrapping the legacy
LegacyRectangle
Translating the new method call to the legacy method call
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:
No access control or additional security
No logging of transactions
Direct manipulation of the account is possible
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:
Protection Proxy:
Adds access control based on user roles
Controls withdrawal limits
Prevents unauthorized actions
Logging Proxy:
Maintains a transaction log
Tracks all withdrawal attempts
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:
Remote Proxy: Represents an object located in a different system
Virtual Proxy: Provides placeholder functionality until the real object is needed
Protection Proxy: Controls access to the original object
Caching Proxy: Stores and returns cached results
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:
Decoupling:
The publisher (Subject) doesn't know the specific details of its subscribers
Each subscriber implements the
Observer
interface independently
Flexibility:
Easy to add new types of subscribers without modifying existing code
Subscribers can be added or removed dynamically
Design Principles Followed:
Open/Closed Principle: Open for extension, closed for modification
Dependency Inversion Principle: Depend on abstractions (Observer interface)
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:
Define an Observer interface
Create a Subject (publisher) with attach, detach, and notify methods
Implement concrete observers that follow the Observer interface
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
Iterator Interface: Defines methods for traversing elements
has_next()
: Checks if more elements existnext()
: Retrieves the next element
Concrete Iterator: Implements the traversal logic
Keeps track of the current position
Provides methods to access elements
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
Python has built-in iterator support through the
__iter__()
and__next__()
methodsMany languages provide native iterator interfaces
Can be extended to support more complex traversal strategies
The example demonstrates two approaches:
A custom iterator implementation
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 .
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