top of page
jaykumar3

Design Patterns

Design patterns are reusable solutions to common problems that occur in software design. They represent best practices for solving specific design problems and provide general templates for creating robust and maintainable software. These patterns are not frameworks or libraries; instead, they are templates that can be adapted to address various situations in software development.


There are three main types of design patterns:

  1. Creational design patterns

  2. Structural design patterns

  3. Behavioral design patterns

They are subdivided into many different patterns. Let's get started with Creational design patterns.

1) Creational Design Pattern

Singleton

The Singleton Design Pattern is a creational pattern that ensures a class has only one instance and provides a global point of access to that instance. It is often used when exactly one object is needed to coordinate actions across the system. The Singleton pattern is commonly employed to control access to resources such as database connections or logging services.



Here's a simple example of how you might implement the Singleton pattern in Java:

public class Singleton {
    private static Singleton instance;
    
    private Singleton() {
    }
    
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

Builder

The Builder Design Pattern is a creational pattern that separates the construction of a complex object from its representation. It allows the same construction process to create different representations of an object. This pattern is especially useful when an object needs to be created with a large number of optional parameters, and not all combinations of those parameters make sense.


Example:

// Product class representing the order
class Order {
    private String burger;
    private String fries;
    private String drink;

    public void setBurger(String burger) {
        this.burger = burger;
    }

    public void setFries(String fries) {
        this.fries = fries;
    }

    public void setDrink(String drink) {
        this.drink = drink;
    }

    @Override
    public String toString() {
        return "Order: Burger - " + burger + ", Fries - " + fries + ", Drink - " + drink;
    }
}

// Builder interface defining the steps to build an order
interface RestaurantCrew {
    void buildBurger();
    void buildFries();
    void buildDrink();
    Order getOrder();
}

// Concrete builder implementing the steps to build an order
class ConcreteRestaurantCrew implements RestaurantCrew {
    private Order order;

    public ConcreteRestaurantCrew() {
        this.order = new Order();
    }

    @Override
    public void buildBurger() {
        order.setBurger("Classic Burger");
    }

    @Override
    public void buildFries() {
        order.setFries("Large Fries");
    }

    @Override
    public void buildDrink() {
        order.setDrink("Cola");
    }

    @Override
    public Order getOrder() {
        return order;
    }
}

// Director class (Cashier) that instructs the builder to build an order
class Cashier {
    private RestaurantCrew restaurantCrew;

    public Cashier(RestaurantCrew restaurantCrew) {
        this.restaurantCrew = restaurantCrew;
    }

    public void takeOrder() {
        restaurantCrew.buildBurger();
        restaurantCrew.buildFries();
        restaurantCrew.buildDrink();
    }

    public Order serveOrder() {
        return restaurantCrew.getOrder();
    }
}

// Client (Customer) code
public class Customer {
    public static void main(String[] args) {
        // Customer wants to order a meal
        RestaurantCrew restaurantCrew = new ConcreteRestaurantCrew();
        Cashier cashier = new Cashier(restaurantCrew);

        // Cashier takes the order
        cashier.takeOrder();

        // Cashier serves the order to the customer
        Order order = cashier.serveOrder();

        // Display the order
        System.out.println(order);
    }
}

2) Structural design patterns

Adapter

When two incompatible interfaces cannot be connected directly, an adapter pattern serves as a bridge. This pattern's primary objective is to change an existing interface into one that the client expects.


This pattern's structure is comparable to that of the Decorator. Nonetheless, the extension is typically taken into consideration when using the Decorator. Usually, when the initial code to connect incompatible interfaces is developed, the Adapter is implemented.




Example:

// Target interface representing a basic socket
interface BasicSocket {
    void plugIn();
}

// Adaptee class representing a basic socket implementation
class ConcreteBasicSocket implements BasicSocket {
    @Override
    public void plugIn() {
        System.out.println("Plugged into a basic socket.");
    }
}

// Adapter class adapting a basic socket to a ratchet socket
class RatchetSocketAdapter implements BasicSocket {
    private ConcreteBasicSocket basicSocket;

    public RatchetSocketAdapter(ConcreteBasicSocket basicSocket) {
        this.basicSocket = basicSocket;
    }

    @Override
    public void plugIn() {
        basicSocket.plugIn();
        System.out.println("Converted to a ratchet socket.");
    }
}

// Client code using a ratchet socket
public class Client {
    public static void main(String[] args) {
        // Using a basic socket
        ConcreteBasicSocket basicSocket = new ConcreteBasicSocket();
        basicSocket.plugIn();

        // Adapting a basic socket to a ratchet socket using an adapter
        RatchetSocketAdapter ratchetAdapter = new RatchetSocketAdapter(basicSocket);
        ratchetAdapter.plugIn();
    }
}

Decorator

A structural pattern called the Decorator Design Pattern makes it possible to add functionality to a single object—either statically or dynamically—without influencing the behavior of other objects in the same class. It is helpful for adding features to classes in a reusable and adaptable manner.




Example:

// Component interface representing the base weapon
interface BaseWeapon {
    void fire();
}

// Concrete component representing a basic weapon
class ConcreteWeapon implements BaseWeapon {
    @Override
    public void fire() {
        System.out.println("Firing a basic weapon");
    }
}

// Decorator interface representing weapon accessories
interface WeaponAccessory extends BaseWeapon {
}

// Concrete decorator representing a scope accessory
class Scope implements WeaponAccessory {
    private BaseWeapon weapon;

    public Scope(BaseWeapon weapon) {
        this.weapon = weapon;
    }

    @Override
    public void fire() {
        weapon.fire();
        System.out.println("with a Scope");
    }
}

// Concrete decorator representing a silencer accessory
class Silencer implements WeaponAccessory {
    private BaseWeapon weapon;

    public Silencer(BaseWeapon weapon) {
        this.weapon = weapon;
    }

    @Override
    public void fire() {
        weapon.fire();
        System.out.println("with a Silencer");
    }
}

// Client code using the decorated weapon
public class Client {
    public static void main(String[] args) {
        // Creating a basic weapon
        BaseWeapon basicWeapon = new ConcreteWeapon();
        basicWeapon.fire();

        // Decorating the weapon with a scope
        WeaponAccessory scopedWeapon = new Scope(basicWeapon);
        scopedWeapon.fire();

        // Decorating the weapon with a silencer
        WeaponAccessory silencedWeapon = new Silencer(basicWeapon);
        silencedWeapon.fire();

        // Decorating the weapon with both a scope and a silencer
        WeaponAccessory scopedAndSilencedWeapon = new Silencer(new Scope(basicWeapon));
        scopedAndSilencedWeapon.fire();
    }
}

3) Behavioral Patterns

Strategy

The Strategy Design Pattern is a behavioral pattern that defines a family of algorithms, encapsulates each algorithm, and makes them interchangeable. It allows a client to choose an algorithm from a family of algorithms at runtime, independently of the clients that use it. The pattern defines a common interface for all supported algorithms, making them interchangeable.



Example

// Strategy interface representing different transportation strategies
interface TravelStrategy {
    void travel();
}

// Concrete strategy representing traveling to the airport by car
class Car implements TravelStrategy {
    @Override
    public void travel() {
        System.out.println("Traveling to the airport by car");
    }
}

// Concrete strategy representing traveling to the airport by city bus
class CityBus implements TravelStrategy {
    @Override
    public void travel() {
        System.out.println("Traveling to the airport by city bus");
    }
}

// Concrete strategy representing traveling to the airport by taxi
class Taxi implements TravelStrategy {
    @Override
    public void travel() {
        System.out.println("Traveling to the airport by taxi");
    }
}

// Context class that uses a travel strategy
class TransportationToAirport {
    private TravelStrategy travelStrategy;

    public void setTravelStrategy(TravelStrategy travelStrategy) {
        this.travelStrategy = travelStrategy;
    }

    public void goToAirport() {
        System.out.print("Starting journey: ");
        travelStrategy.travel();
    }
}

// Client code using different transportation strategies
public class Client {
    public static void main(String[] args) {
        // Creating transportation context
        TransportationToAirport transportationContext = new TransportationToAirport();

        // Traveling to the airport by car
        transportationContext.setTravelStrategy(new Car());
        transportationContext.goToAirport();

        // Traveling to the airport by city bus
        transportationContext.setTravelStrategy(new CityBus());
        transportationContext.goToAirport();

        // Traveling to the airport by taxi
        transportationContext.setTravelStrategy(new Taxi());
        transportationContext.goToAirport();
    }
}

Template

An algorithm's skeleton is defined in the superclass using the Template Method Design Pattern, a behavioral pattern that permits subclasses to override certain stages without altering the algorithm's overall structure. It's employed when you have a standard algorithm that requires a few minor adjustments to specific steps.



Example

// Abstract class representing a Worker
abstract class Worker {
    // Template method defining the daily routine of a worker
    public final void workDay() {
        arriveAtWork();
        performDuties();
        departFromWork();
    }

    // Concrete methods shared by all workers
    private void arriveAtWork() {
        System.out.println("Arriving at work");
    }

    private void departFromWork() {
        System.out.println("Leaving work");
    }

    // Abstract method to be implemented by subclasses
    protected abstract void performDuties();
}

// Concrete class representing a Firefighter
class Firefighter extends Worker {
    @Override
    protected void performDuties() {
        System.out.println("Fighting fires");
    }
}

// Concrete class representing a Lumberjack
class Lumberjack extends Worker {
    @Override
    protected void performDuties() {
        System.out.println("Cutting down trees");
    }
}

// Concrete class representing a Postman
class Postman extends Worker {
    @Override
    protected void performDuties() {
        System.out.println("Delivering mail");
    }
}

// Concrete class representing a Manager
class Manager extends Worker {
    @Override
    protected void performDuties() {
        System.out.println("Managing the team");
    }
}

// Client code using the template method
public class Client {
    public static void main(String[] args) {
        // Creating instances of different workers
        Worker firefighter = new Firefighter();
        Worker lumberjack = new Lumberjack();
        Worker postman = new Postman();
        Worker manager = new Manager();

        // Performing the daily routine for each worker
        System.out.println("Firefighter's Day:");
        firefighter.workDay();

        System.out.println("\nLumberjack's Day:");
        lumberjack.workDay();

        System.out.println("\nPostman's Day:");
        postman.workDay();

        System.out.println("\nManager's Day:");
        manager.workDay();
    }
}

Conclusion

Design patterns are incredibly useful resources for resolving typical software development issues. Developers can produce code that is more scalable, efficient, and maintainable by comprehending and utilizing these patterns. Every pattern gives a tested fix for a particular problem.



Image Sources












5 views0 comments

Recent Posts

See All

Commentaires


bottom of page