Use of design patterns in software development

Design patterns are a fundamental aspect of software development, providing a common vocabulary and structure for solving common problems. Understanding and effectively utilizing design patterns can greatly improve the quality, maintainability, and scalability of your code. In this blog post, we will explore some of the most popular design patterns used in software development, including the Factory pattern, the Singleton pattern, and the Observer pattern.

Factory pattern

The Factory pattern is a creational design pattern that provides a way to create objects without specifying the exact class of object that will be created. This allows for a level of abstraction and flexibility in the creation of objects. The Factory pattern can be implemented in several ways, but a common approach is to create a Factory class that includes a method for creating objects of a specific type. This method can be overridden by subclasses to create objects of different classes, depending on the needs of the application.

// The interface for creating an object
interface Shape {
    draw(): void;
}

// The concrete classes that implement the Shape interface
class Circle implements Shape {
    draw() {
        console.log("Drawing a Circle");
    }
}

class Square implements Shape {
    draw() {
        console.log("Drawing a Square");
    }
}

// The factory class that creates the objects
class ShapeFactory {
    getShape(shapeType: string): Shape {
        if (shapeType === "Circle") {
            return new Circle();
        } else if (shapeType === "Square") {
            return new Square();
        } else {
            return null;
        }
    }
}

// Usage
const factory = new ShapeFactory();
const circle = factory.getShape("Circle");
circle.draw(); // Output: Drawing a Circle

const square = factory.getShape("Square");
square.draw(); // Output: Drawing a Square

Singleton pattern

The Singleton pattern is a creational design pattern that ensures a class has only one instance while providing a global access point to that instance. This is often useful when only a single instance of a class should exist to control the action throughout the execution. A singleton class should have a private constructor, a private static instance of itself, and a static method that returns the singleton instance.

class Singleton {
    private static instance: Singleton;
    private constructor() {}

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

    // other methods
}

// Usage
const singleton1 = Singleton.getInstance();
const singleton2 = Singleton.getInstance();

console.log(singleton1 === singleton2); // Output: true

Observer pattern

The Observer pattern is a behavioral design pattern that allows an object to notify other objects about changes to its state. This pattern is often used in event-driven systems, where one object (the subject) needs to notify other objects (the observers) about changes to its state. The Observer pattern can be implemented using interfaces or abstract classes, with the subject and observer classes implementing the necessary methods for registering and unregistering observers, and for updating the observers when the subject's state changes.

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

interface Subject {
    registerObserver(observer: Observer): void;
    removeObserver(observer: Observer): void;
    notifyObservers(): void;
}

class ConcreteObserverA implements Observer {
    update(data: any) {
        console.log(`Observer A received data: ${data}`);
    }
}

class ConcreteObserverB implements Observer {
    update(data: any) {
        console.log(`Observer B received data: ${data}`);
    }
}

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

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

    removeObserver(observer: Observer) {
        const index = this.observers.indexOf(observer);
        this.observers.splice(index, 1);
    }

    notifyObservers() {
        for (const observer of this.observers) {
            observer.update("example data");
        }
    }
}

// Usage
const subject = new ConcreteSubject();
const observerA = new ConcreteObserverA();
const observerB = new ConcreteObserverB();

subject.registerObserver(observerA);
subject.registerObserver(observerB);
subject.notifyObservers();

// Output: Observer A received data: example data
// Output: Observer B received data: example data

In conclusion, design patterns provide a common language and structure for solving common problems in software development. By understanding and utilizing patterns such as the Factory pattern, the Singleton pattern, and the Observer pattern, you can improve the quality, maintainability, and scalability of your code. It is important to keep in mind that design patterns are not a silver bullet and that each pattern has its trade-offs, as such it's important to understand the problem and context before applying a design pattern.

Did you find this article valuable?

Support Mikaeel Khalid by becoming a sponsor. Any amount is appreciated!