Design Patterns

Design Pattern thumbrules or different concepts using which you can solve the problem of modelling real world examples into object oriented design.

Design Patterns as Thumb Rules:

  • Design patterns serve as thumb rules or concepts to model real-world problems into object-oriented design.
  • They are not language-specific; different languages may implement them in various ways.

  • Implementation Variations:
  • Each design pattern has different implementations based on the programming language.
  • Design patterns are agnostic to languages, representing concepts and principles.

  • Organizing Design Patterns:
  • Design patterns help organize classes, objects, and their behavior and communication.
  • Patterns can be categorized based on their purpose: Creational, Structural, and Behavioral.

  • Purpose-Based Categorization:
  • Creational Patterns: Focus on creating classes or objects and instantiating objects.
  • Structural Patterns: Involve structuring multiple classes and objects, including inheritance and interface segregation.
  • Behavioral Patterns: Address how objects and classes communicate to fulfill complex business use cases.

  • Categorization by Classes and Objects:
  • Patterns can be further classified based on whether they are applicable to classes or objects.
  • Most patterns work on objects, but some are applied specifically to classes.

  • Understanding Categories:
  • Creational Class Patterns: Guide how to create classes.
  • Creational Object Patterns: Focus on instantiating objects for different classes.
  • Structural Class Patterns: Instruct on using inheritance for structuring.
  • Structural Object Patterns: Explain how to organize and assemble objects.
  • Behavioral Class Patterns: Use inheritance for implementing algorithms and control flow.
  • Behavioral Object Patterns: Assemble and write algorithms around objects for complex tasks.
  • LLD - Patterns

    Factory Pattern
    Abstract Factory Pattern
    Singleton Pattern
    Strategy Pattern
    Observer Pattern
    Decorator Pattern

    Factory Pattern

    It is creational pattern that provides an interface for creating instances of a class, with its subclasses deciding which class to instantiate.

    Abstract Factory Pattern

    It is Factory of Factory. when we have different object but we can group them in any way.

    Singleton Pattern

    It is Class Creational design pattern. Singleton means we will create single instance of a class. Make sure that there is no other instance created for this class.

    Observer Pattern

    The Observer pattern is a behavioral design pattern where an object, known as the subject, maintains a list of its dependents, known as observers, that are notified of any changes in the subject's state. This pattern is commonly used to implement distributed event handling systems.

    Strategy Pattern

    The strategy pattern is a behavioral design pattern that enables selecting an algorithm at runtime. Instead of implementing a single algorithm directly, code receives run-time instructions as to which in a family of algorithms to use.

    Strategy pattern, we are trying to separate out dependency of drive from the main class as much as possible by making various strategies to inject it dynamically based on client requirement so that in future if new requirement for drive comes, there will be minimal/no changes in existing code(Open closed Principle).

                
    // With Strategy Pattern
    class VehicleWithStrategy {
      constructor(make, model, driveStrategy) {
        this.make = make;
        this.model = model;
        this.driveStrategy = driveStrategy;
      }
    
      setDriveStrategy(driveStrategy) {
        this.driveStrategy = driveStrategy;
      }
    
      drive(speed) {
        this.driveStrategy.drive(speed, this.make);
      }
    }
    
    // Strategies
    class CarDriveStrategy {
      drive(speed, make) {
        console.log(`Driving the ${make} at ${speed} km/h.`);
      }
    }
    
    class MotorcycleDriveStrategy {
      drive(speed, make) {
        console.log(`Riding the ${make} at ${speed} km/h.`);
      }
    }
    
    // Usage with Strategy Pattern
    const carWithStrategy = new VehicleWithStrategy('Car', 'Sedan', new CarDriveStrategy());
    carWithStrategy.drive(120);
    
    const motorcycleWithStrategy = new VehicleWithStrategy('Motorcycle', 'Sport', new MotorcycleDriveStrategy());
    motorcycleWithStrategy.drive(80);
    
    // Changing strategy dynamically
    carWithStrategy.setDriveStrategy(new MotorcycleDriveStrategy());
    carWithStrategy.drive(90);
    
                
             

    Builder Pattern

    The Builder Pattern is a creational design pattern that is used to construct a complex object step by step. This pattern is particularly useful when an object needs to be created with many optional components or configurations.

    Example
    Let's consider a scenario where we need to create a complex object, such as a Car, with many optional components and configurations. Without the Builder Pattern, you might end up with a constructor that has numerous parameters, making it hard to read, understand, and use correctly. Additionally, not all components are required, and providing default values for each optional component in the constructor could lead to a bloated and inflexible design.

    In this example, creating a Car object requires specifying many parameters, including optional ones. This can lead to confusion about the order of parameters and may result in errors if the wrong values are provided. Additionally, it doesn't provide a clear way to create a Car without specifying all optional components.

    With the Builder Pattern, you can create a Car instance step by step. You only set the components you care about, and the builder provides a clean and readable API for constructing the object. This makes the code more maintainable, and it's easier to understand which components are being configured without the need for a long parameter list. Additionally, it allows for a more flexible and extensible design, as new components or configurations can be added to the builder without modifying the product's class.

                
    // without builder pattern 
    class Car {
        constructor(name, color, year, bluetooth, sunroof, rare_camera, front_camera) {
            this.name = name;
            this.color = color;
            this.year = year;
            this.bluetooth = bluetooth;
            this.sunroof = sunroof;
            this.rare_camera = rare_camera;
            this.front_camera = front_camera;
        }
    
        describe() {
            console.log(`${this.name} ${this.color} ${this.year}`)
        }
    }
    
    let thar1 = new Car('thar', 'black', 2024);
    let thar2 = new Car('thar', 'black', 2024, false, true, true, false);
    
    console.log(thar1)
    console.log(thar2)
    
    // with builder pattern
    class CarBuilder {
        constructor(name, color, year) {
            this.car = new Car(name, color, year)
        }
    
        addBluetooth() {
            this.car.bluetooth = true
            return this;
        }
    
        addSunroof() {
            this.car.sunroof = true
            return this;
        }
    
        addRareCamera() {
            this.car.rare_camera = true;
            return this;
        }
    
        addFrontCamera() {
            this.car.front_camera = true;
            return this;
        }
    
        build() {
            return this.car;
        }
    }
    
    let ertiga = new CarBuilder('ertiga', 'black', 2024).addRareCamera().addSunroof().build();
    
    console.log(ertiga)
                
             

    Decorator Pattern

    The Decorator Pattern is a structural design pattern that enables the dynamic augmentation of an object's behavior, either at compile-time or runtime, without altering the behavior of other instances of the same class. This pattern is particularly useful when there is a need to extend the functionality of objects in a flexible and reusable manner.

    The structure of the Decorator Pattern involves a component interface defining the base functionality, concrete components implementing this interface, decorators that conform to the same interface and enhance the behavior of the components, and a client that interacts with the decorated objects. Decorators can be stacked or combined to add multiple functionalities to an object, allowing for a more modular and extensible design without resorting to a multitude of subclassing.

    This pattern promotes code flexibility and maintainability by providing a means to dynamically tailor the behavior of objects without the need for complex class hierarchies.

    
    // Problematic implementation without Decorator Pattern
    class Coffee {
        constructor() {
            this.description = 'simple coffee',
            this.cost = 5;
        }
    
        getCost() { return this.cost; }
    
        getDescription() { return this.description; }
    }
    
    
    class CoffeeWithMilk extends Coffee {
        constructor() {
            super();
            this.description = 'coffee with milk';
            this.cost = 7;
        }
    
        getCost() { return this.cost; }
        getDescription() { return this.description; }
    }
    
    class CoffeeWithSugar extends Coffee {
        constructor() {
            super();
            this.description = 'coffee with sugar';
            this.cost = 10;
        }
    
        getCost() { return this.cost; }
        getDescription() { return this.description; }
    }
    
    
    class MilkDecorator {
        constructor(coffee) {
            this.coffee = coffee;
        }
    
        getCost() {
            return this.coffee.getCost() + 2;
        }
    
        getDescription() {
            return `${this.coffee.getDescription()} with milk`;
        }
    }
    
    const simpleCoffee = new Coffee;
    
    const milkCoffee = new MilkDecorator(new Coffee());
    
    console.log(milkCoffee.getCost())
             

    Adapter Pattern

    The Adapter Pattern is a structural design pattern that allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces, making them compatible without altering their source code.

    It is used when you have existing code or components with interfaces that don't match the requirements, and you want them to work together seamlessly.

                
    // Existing XML to JSON converter with an incompatible interface
    class XMLtoJSONConverter {
      convert(xmlData) {
        // Simulating the existing XML to JSON conversion logic
        console.log('Converting XML to JSON:', xmlData);
        const jsonData = { result: 'Converted JSON Data' };
        return jsonData;
      }
    }
    
    // New interface expected by the application
    class JSONConverter {
      convertJSON(jsonData) {
        console.log('Displaying JSON Data:', jsonData);
      }
    }
    
    // Adapter to make XMLtoJSONConverter compatible with JSONConverter
    class XMLtoJSONConverterAdapter extends JSONConverter {
      constructor(xmlToJSONConverter) {
        super();
        this.xmlToJSONConverter = xmlToJSONConverter;
      }
    
      convertJSON() {
        const xmlData = 'XML Data'; // Simulated XML data
        const jsonData = this.xmlToJSONConverter.convert(xmlData);
        super.convertJSON(jsonData);
      }
    }
    
    // Usage with Adapter
    const xmlToJSONConverter = new XMLtoJSONConverter();
    const xmlToJSONConverterAdapter = new XMLtoJSONConverterAdapter(xmlToJSONConverter);
    
    // Now, the XMLtoJSONConverter can be used seamlessly with the JSONConverter interface
    xmlToJSONConverterAdapter.convertJSON();
                
             

    Facade Pattern

    Facade pattern hides the complexities of the system and provides an interface to the client using which the client can access the system. This type of design pattern comes under structural pattern as this pattern adds an interface to existing system to hide its complexities.

    This pattern involves a single class which provides simplified methods required by client and delegates calls to methods of existing system classes.

    
    // Interface: Shape
    class Shape {
      draw() {
        console.log("Drawing a shape");
      }
    }
    
    // Concrete Class: Rectangle
    class Rectangle extends Shape {
      draw() {
        console.log("Drawing a Rectangle");
      }
    }
    
    // Concrete Class: Square
    class Square extends Shape {
      draw() {
        console.log("Drawing a Square");
      }
    }
    
    // Concrete Class: Circle
    class Circle extends Shape {
      draw() {
        console.log("Drawing a Circle");
      }
    }
    
    // Facade: ShapeMaker
    class ShapeMaker {
      constructor() {
        this.circle = new Circle();
        this.rectangle = new Rectangle();
        this.square = new Square();
      }
    
      drawCircle() {
        this.circle.draw();
      }
    
      drawRectangle() {
        this.rectangle.draw();
      }
    
      drawSquare() {
        this.square.draw();
      }
    }
    
    // FacadePatternDemo
    const shapeMaker = new ShapeMaker();
    
    shapeMaker.drawCircle();
    shapeMaker.drawRectangle();
    shapeMaker.drawSquare();
    
             

    Resources

    https://cseweb.ucsd.edu//~wgg/CSE210/ecoop93-patterns.pdf