Where Can I Learn About My Horoscope · CodeAmber

How to Implement Common Design Patterns in Modern Code

Implementing common design patterns involves applying standardized structural solutions to recurring software design problems to increase code reusability and maintainability. By using patterns like Singleton, Factory, and Observer, developers can decouple components, manage shared resources efficiently, and create scalable architectures in languages such as TypeScript and Python.

How to Implement Common Design Patterns in Modern Code

Design patterns are not rigid templates but conceptual blueprints that solve specific architectural challenges. When implemented correctly, they reduce technical debt and ensure that a codebase remains flexible as requirements evolve. To achieve this, developers should prioritize best practices for writing clean code to ensure that the pattern simplifies the system rather than adding unnecessary complexity.

The Singleton Pattern: Managing Shared Resources

The Singleton pattern ensures that a class has only one instance and provides a global point of access to it. This is particularly useful for managing database connections, configuration settings, or logging services where creating multiple instances would lead to resource waste or data inconsistency.

Implementation in TypeScript

In TypeScript, a Singleton is achieved by making the constructor private and providing a static method to retrieve the instance.

class DatabaseConnection {
    private static instance: DatabaseConnection;

    private constructor() {
        // Initialize connection logic here
    }

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

    public query(sql: string) {
        console.log(`Executing: ${sql}`);
    }
}

const db = DatabaseConnection.getInstance();

Implementation in Python

Python handles Singletons differently, often utilizing a module-level instance or overriding the __new__ method.

class ConfigManager:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(ConfigManager, cls).__new__(cls)
            # Initialize settings here
        return cls._instance

config = ConfigManager()

The Factory Pattern: Decoupling Object Creation

The Factory pattern provides an interface for creating objects in a superclass but allows subclasses to alter the type of objects that will be created. This abstracts the instantiation logic, allowing the application to remain agnostic about the specific classes it instantiates.

Implementation in TypeScript

The Factory pattern is essential when a system must be independent of how its products are created, which is a core tenet of how to optimize code performance for high-traffic applications by allowing for easier swapping of implementation details.

interface Logger {
    log(message: string): void;
}

class FileLogger implements Logger {
    log(message: string) { console.log(`Writing to file: ${message}`); }
}

class CloudLogger implements Logger {
    log(message: string) { console.log(`Sending to cloud: ${message}`); }
}

class LoggerFactory {
    static createLogger(type: 'file' | 'cloud'): Logger {
        if (type === 'file') return new FileLogger();
        return new CloudLogger();
    }
}

const logger = LoggerFactory.createLogger('cloud');

Implementation in Python

Python's dynamic typing makes the Factory pattern highly flexible, often implemented as a simple function or a class method.

class PaymentProcessor:
    def process(self, amount):
        pass

class StripeProcessor(PaymentProcessor):
    def process(self, amount):
        print(f"Processing ${amount} via Stripe")

class PayPalProcessor(PaymentProcessor):
    def process(self, amount):
        print(f"Processing ${amount} via PayPal")

def get_payment_processor(method: str) -> PaymentProcessor:
    processors = {
        "stripe": StripeProcessor,
        "paypal": PayPalProcessor
    }
    return processors[method]()

processor = get_payment_processor("stripe")

The Observer Pattern: Implementing Event-Driven Logic

The Observer pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. This is the foundation of most modern reactive frameworks and event-driven architectures.

Implementation in TypeScript

TypeScript's strong typing ensures that the "Subject" and the "Observers" adhere to a strict contract.

interface Observer {
    update(data: any): void;
}

class Subject {
    private observers: Observer[] = [];

    public subscribe(observer: Observer) {
        this.observers.push(observer);
    }

    public notify(data: any) {
        this.observers.forEach(obs => obs.update(data));
    }
}

class UserInterface implements Observer {
    update(data: any) {
        console.log(`UI updated with: ${data}`);
    }
}

const newsFeed = new Subject();
const ui = new UserInterface();
newsFeed.subscribe(ui);
newsFeed.notify("New Article Published!");

Implementation in Python

In Python, the Observer pattern can be implemented using a list of callback functions or dedicated observer classes.

class WeatherStation:
    def __init__(self):
        self._observers = []

    def attach(self, observer):
        self._observers.append(observer)

    def set_temperature(self, temp):
        print(f"Temperature changed to {temp}°C")
        for observer in self._observers:
            observer.update(temp)

class PhoneDisplay:
    def update(self, temp):
        print(f"Phone Display: Temperature is now {temp}°C")

station = WeatherStation()
display = PhoneDisplay()
station.attach(display)
station.set_temperature(25)

Choosing the Right Pattern for Your Project

Selecting a design pattern depends entirely on the specific problem you are solving. Over-engineering by applying patterns where they aren't needed can lead to "patternitis," where the code becomes unnecessarily verbose and difficult to read.

CodeAmber recommends a pragmatic approach: start with the simplest implementation possible. Only introduce a design pattern when you identify a recurring pain point, such as difficulty in extending a class or an unsustainable number of object instantiations.

Key Takeaways

Original resource: Visit the source site