Design Patterns - All in one

Design Patterns - All in one

Design Pattern categories

There are several categories of design patterns, including:

  1. Creational patterns: These patterns deal with object creation mechanisms, trying to create objects in a manner suitable to the situation. Examples include the factory pattern, the builder pattern, and the prototype pattern.

  2. Structural patterns: These patterns deal with object composition, creating relationships between objects to form larger structures. Examples include the adapter pattern, the decorator pattern, and the proxy pattern.

  3. Behavioral patterns: These patterns focus on communication between objects, what goes on between objects and how they operate together. Examples include the observer pattern, the iterator pattern, and the strategy pattern.

  4. Concurrency patterns: These patterns deal with multi-threaded programming and the communication between threads. Examples include the monitor pattern and the lock pattern.

  5. Architectural patterns: These patterns deal with the overall structure of software systems and the relationships between different components. Examples include the Model-View-Controller (MVC) pattern and the Model-View-ViewModel (MVVM) pattern.

Builder Design Pattern

The Builder design pattern is a design pattern that allows for the step-by-step creation of complex objects using the correct sequence of actions. This pattern is often used in the construction of objects such as buildings, vehicles, and other complex structures where the process of creating the object must be carefully orchestrated. The Builder pattern allows for the creation of objects to be easily customized, without the need for a complex, multi-step construction process.

One real-world example of the builder pattern is the construction of a car. The process of building a car involves many different steps, such as assembling the engine, attaching the wheels, and installing the interior. Each of these steps is a separate and complex process in itself, but the builder pattern allows these steps to be abstracted and grouped together into a single, intuitive process for building a car.

Here is a short example of the ‌Builder:


public class User
{
    //All final attributes
    private final String firstName; // required
    private final String lastName; // required
    private final int age; // optional
    private final String phone; // optional
    private final String address; // optional

    private User(UserBuilder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.age = builder.age;
        this.phone = builder.phone;
        this.address = builder.address;
    }

    //All getter, and NO setter to provde immutability
    public String getFirstName() {
        return firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public int getAge() {
        return age;
    }
    public String getPhone() {
        return phone;
    }
    public String getAddress() {
        return address;
    }

    @Override
    public String toString() {
        return "User: "+this.firstName+", "+this.lastName+", "+this.age+", "+this.phone+", "+this.address;
    }

    public static class UserBuilder
    {
        private final String firstName;
        private final String lastName;
        private int age;
        private String phone;
        private String address;

        public UserBuilder() {}

        public UserBuilder firstName(String firstName) {
            this.firstName = firstName;
            return this;
        }

        public UserBuilder lastName(String lastName) {
            this.lastName = lastName;
            return this;
        }

        public UserBuilder age(int age) {
            this.age = age;
            return this;
        }
        public UserBuilder phone(String phone) {
            this.phone = phone;
            return this;
        }
        public UserBuilder address(String address) {
            this.address = address;
            return this;
        }
        //Return the finally consrcuted User object
        public User build() {
            User user =  new User(this);
            validateUserObject(user);
            return user;
        }
        private void validateUserObject(User user) {
            //Do some basic validations to check
            //if user object does not break any assumption of system
        }
    }
}

User user1 = new User.UserBuilder("Lokesh", "Gupta")
    .age(30)
    .phone("1234567")
    .address("Fake address 1234")
    .build();

    System.out.println(user1);

Director in Builder design pattern

In the Builder design pattern, the Director class is responsible for overseeing the construction of an object using the Builder interface. The Director class is typically used when the construction process for an object is complex and must be performed in a specific order. The Director class provides a high-level interface for creating the object, and delegates the construction of the object to the Builder implementation. This allows for the construction process to be easily customized, without the need for a complex, multi-step construction process.

Here is a example of the Director class in action:

// The Director class that constructs an object using the
// Builder interface
class CarDealer {
  private CarBuilder builder;

  public CarDealer(CarBuilder builder) {
    this.builder = builder;
  }

  // Construct a car using the CarBuilder interface
  public void constructCar() {
    builder.setModel();
    builder.setEngine();
    builder.setTransmission();
    builder.setBody();
    builder.setDoors();
  }
}

// The Builder interface that all builders must implement
interface CarBuilder {
  public void setModel();
  public void setEngine();
  public void setTransmission();
  public void setBody();
  public void setDoors();
}

// A Concrete Builder class that builds a car
// step by step
class FordMustangBuilder implements CarBuilder {
  private Car car;

  public FordMustangBuilder() {
    car = new Car();
  }

  public void setModel() {
    car.setModel("Mustang");
  }

  public void setEngine() {
    Engine engine = new Engine();
    engine.setPower(400);
    engine.setType("V8");
    car.setEngine(engine);
  }

  public void setTransmission() {
    Transmission transmission = new Transmission();
    transmission.setType("Manual");
    transmission.setNumberOfGears(6);
    car.setTransmission(transmission);
  }

  public void setBody() {
    Body body = new Body();
    body.setType("Coupe");
    body.setColor("Red");
    car.setBody(body);
  }

  public void setDoors() {
    car.setNumberOfDoors(2);
  }

  public Car getResult() {
    return car;
  }
}

// The Car class that is being constructed
class Car {
  private String model;
  private Engine engine;
  private Transmission transmission;
  private Body body;
  private int numberOfDoors;

  public void setModel(String model) {
    this.model = model;
  }

  public void setEngine(Engine engine) {
    this.engine = engine;
  }

  public void setTransmission(Transmission transmission) {
    this.transmission = transmission;
  }

  public void setBody(Body body) {
    this.body = body;
  }

  public void setNumberOfDoors(int numberOfDoors) {
    this.numberOfDoors = numberOfDoors;
  }
}

// Usage
CarBuilder builder = new FordMustangBuilder();
CarDealer dealer = new CarDealer(builder);
dealer.constructCar();
Car car = builder.getResult();

Facade

The facade design pattern is a software design pattern that provides a simplified interface to a complex system of components. The idea behind the facade pattern is to provide a single, easy-to-use interface that hides the complexity of the underlying system and allows clients to interact with the system in a simple and intuitive way. This can help to reduce the learning curve for new users and make the system easier to use overall.

One real-world example of the facade pattern is the way that a TV remote control works. The remote control provides a simple interface with just a few buttons, but behind the scenes it communicates with a complex system of components inside the TV, such as the power supply, the display, and the speakers. By pressing a single button on the remote control, the user can turn the TV on or off, adjust the volume, or change the channel, without needing to understand the details of how the TV's internal components work. The remote control acts as a facade, providing a simple and intuitive interface to the underlying system.

Here is a simple example of the facade pattern implemented in Python:

# complex system of components that we want to provide a simplified interface for
class CPU:
    def freeze(self):
        print("Freezing processor.")

    def jump(self, position):
        print(f"Jumping to position {position}.")

    def execute(self):
        print("Executing instruction.")


class Memory:
    def load(self, position, data):
        print(f"Loading from {position} data: {data}.")


class HardDrive:
    def read(self, lba, size):
        return f"Data from hard drive, lba: {lba}, size: {size}"


# facade class that provides a simplified interface for the complex system
class Computer:
    def __init__(self):
        self.cpu = CPU()
        self.memory = Memory()
        self.hard_drive = HardDrive()

    def start_computer(self):
        self.cpu.freeze()
        self.memory.load(0, self.hard_drive.read(0, 1024))
        self.cpu.jump(10)
        self.cpu.execute()


# client code that uses the facade
if __name__ == "__main__":
    computer = Computer()
    computer.start_computer()

Proxy

Is a software design pattern that provides a placeholder or surrogate for another object in order to control access to that object. The proxy acts as an intermediary between the client and the real object, and can be used to perform a variety of tasks such as logging, caching, or security checks before forwarding requests to the real object.

For example, a proxy class for a network connection could be created that authenticates the client and logs all requests before forwarding them to the real network connection object. This allows the proxy to add additional functionality and security to the network connection without changing the client code or the real object. The proxy class can also be reused for different types of network connections, making it a flexible and powerful tool for managing access to complex systems.

The proxy design pattern is typically used when there is a need to control access to an object, or to add additional functionality to an object without changing the object itself. Some common scenarios where the proxy pattern may be useful include:

  1. Implementing security checks or authentication mechanisms for accessing an object. For example, a proxy class for a database connection could authenticate the client and check their permissions before forwarding requests to the real database connection.

  2. Providing a local cache for frequently-accessed data. For example, a proxy for a network connection could cache the results of frequently-made requests, reducing the number of requests made to the network and improving performance.

  3. Adding logging or monitoring capabilities to an object. For example, a proxy for a network connection could log all requests and responses, providing a record of the communication between the client and the network.

  4. Hiding the complexity of a system from the client. For example, a proxy for a distributed system could provide a simple and intuitive interface to the client, abstracting away the details of the underlying system and making it easier to use.

Here is a simple example of the proxy pattern implemented in Python:

# the real object that the proxy will provide access to
class NetworkConnection:
    def request(self, data):
        print(f"Sending request: {data}")
        return "Response data"


# the proxy class that controls access to the real object
class NetworkConnectionProxy:
    def __init__(self):
        self.is_authenticated = False

    def authenticate(self, username, password):
        if username == "admin" and password == "password":
            self.is_authenticated = True
            print("Authentication successful.")
        else:
            print("Authentication failed.")

    def request(self, data):
        if not self.is_authenticated:
            print("Authentication required.")
            return

        # create the real object and forward the request to it
        network_connection = NetworkConnection()
        return network_connection.request(data)


# client code that uses the proxy
if __name__ == "__main__":
    proxy = NetworkConnectionProxy()

    # authentication is required before making requests
    proxy.request("important data")
    proxy.authenticate("admin", "password")
    proxy.request("important data")

Here is another example of the proxy pattern implemented in Python, this time using a proxy to provide a local cache for frequently-accessed data:

# the real object that the proxy will provide access to
class NetworkConnection:
    def request(self, url):
        print(f"Making request to: {url}")
        return "Response data"


# the proxy class that provides a local cache for frequently-accessed data
class NetworkConnectionProxy:
    def __init__(self):
        self.cache = {}

    def request(self, url):
        # check the local cache for the requested data
        if url in self.cache:
            print("Retrieving from cache.")
            return self.cache[url]

        # create the real object and forward the request to it
        network_connection = NetworkConnection()
        data = network_connection.request(url)

        # store the response in the cache and return it to the client
        self.cache[url] = data
        return data


# client code that uses the proxy
if __name__ == "__main__":
    proxy = NetworkConnectionProxy()

    # make requests to the same URL multiple times
    data = proxy.request("http://www.example.com")
    print(data)
    data = proxy.request("http://www.example.com")
    print(data)

Facade vs Proxy

While the facade and proxy patterns both provide a simplified interface to a complex system, they have some key differences. The facade pattern is intended to provide a straightforward and easy-to-use interface to a complex system, hiding the details of the underlying components and making the system easier to use overall. The proxy pattern, on the other hand, is focused on controlling access to an object and adding additional functionality, rather than providing a simpler interface.

Additionally, the facade pattern is typically used to provide a unified interface to a system that is made up of multiple, independent components. The proxy pattern, on the other hand, is typically used to provide a single interface to a single object, with the proxy class acting as a placeholder or surrogate for that object.

Overall, while the facade and proxy patterns both provide a simplified interface to a complex system, they have different goals and are used in different situations.

Strategy

It's a software design pattern that allows a behavior or algorithm to be selected at runtime. The strategy pattern defines a family of algorithms, encapsulates each algorithm, and makes them interchangeable. This allows the client code to choose the appropriate algorithm at runtime, depending on the specific needs of the application.

For example, a strategy class for sorting could be created that has different algorithms for sorting a list of numbers, such as insertion sort, bubble sort, and quick sort. The client code could then use the strategy class to sort a list of numbers, choosing the appropriate algorithm at runtime depending on the size of the list and the desired performance characteristics. This allows the client code to use different sorting algorithms without needing to change the code that uses them, making the code more flexible and reusable.

// The strategy interface defines the behavior that all strategies must implement
public interface SortStrategy {
  void sort(int[] array);
}

// Concrete strategy implementation that uses bubble sort
public class BubbleSortStrategy implements SortStrategy {
  public void sort(int[] array) {
    // Implement bubble sort algorithm here
  }
}

// Concrete strategy implementation that uses quicksort
public class QuickSortStrategy implements SortStrategy {
  public void sort(int[] array) {
    // Implement quicksort algorithm here
  }
}

// The context class maintains a reference to the chosen strategy object
// and provides a way to set the strategy at runtime
public class Sorter {
  private SortStrategy strategy;

  public Sorter(SortStrategy strategy) {
    this.strategy = strategy;
  }

  public void setStrategy(SortStrategy strategy) {
    this.strategy = strategy;
  }

  public void sort(int[] array) {
    // In here with switch case we can decide using each algorithm depend on size of the array   
    strategy.sort(array);
  }
}

// Example usage
int[] array = {3, 5, 1, 4, 2};

Sorter sorter = new Sorter(new BubbleSortStrategy());
sorter.sort(array);

// Output: 1 2 3 4 5

// We can change the sorting strategy at runtime
sorter.setStrategy(new QuickSortStrategy());
sorter.sort(array);

// Output: 1 2 3 4 5

Another example of the strategy pattern is the use of different payment methods in an online shopping website. The website may offer different payment methods, such as credit card, PayPal, or bank transfer, and each payment method has its own algorithm for processing the payment. The strategy pattern can be used to implement the payment processing in a flexible and reusable way, allowing the client code to choose the appropriate payment method at runtime without needing to change the code that processes the payment.

For example, a Payment class could be created that has different strategy classes for each payment method, such as CreditCardPayment, PayPalPayment, and BankTransferPayment. The Payment class could then have a method for processing the payment that accepts a PaymentStrategy object and uses it to process the payment. The client code could then create an instance of the appropriate PaymentStrategy class and pass it to the Payment class to process the payment, choosing the appropriate payment method at runtime. This allows the client code to use different payment methods without needing to change the code that processes the payment, making the code more flexible and reusable.


// The strategy interface
public interface PaymentStrategy {
    void pay(int amount);
}

// Concrete strategy 1
public class CreditCardPayment implements PaymentStrategy {
    private String name;
    private String cardNumber;
    private String cvv;
    private String dateOfExpiry;

    public CreditCardPayment(String name, String cardNumber, String cvv, String dateOfExpiry) {
        this.name = name;
        this.cardNumber = cardNumber;
        this.cvv = cvv;
        this.dateOfExpiry = dateOfExpiry;
    }

    @Override
    public void pay(int amount) {
        System.out.println(amount + " paid with credit/debit card");
    }
}

// Concrete strategy 2
public class PayPalPayment implements PaymentStrategy {
    private String email;
    private String password;

    public PayPalPayment(String email, String password) {
        this.email = email;
        this.password = password;
    }

    @Override
    public void pay(int amount) {
        System.out.println(amount + " paid using PayPal");
    }
}

// The context class
public class ShoppingCart {
    private List<Item> items;

    public ShoppingCart(List<Item> items) {
        this.items = items;
    }

    public int calculateTotal() {
        int sum = 0;
        for (Item item : items) {
            sum += item.getPrice();
        }
        return sum;
    }

    public void pay(PaymentStrategy paymentMethod) {
        // In here we can decide to use which payment method by paymentRequest object field or another way.
        int amount = calculateTotal();
        paymentMethod.pay(amount);
    }
}

// The client code
public class Main {
    public static void main(String[] args) {
        List<Item> items = new ArrayList<>();
        items.add(new Item("Shirt", 25));
        ShoppingCart shp = new ShoppingCart(items);
        shp.pay(new PayPalPayment());
     }

Factory

The factory design 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 is done by creating a factory class that has a method for creating objects, and the objects are created based on the data or information that is provided to the factory.

The factory design pattern is useful when you have a system that needs to be able to create objects of different types, but you don't want to have to specify the exact class of object that will be created. This allows for more flexibility and modularity in the system, and makes it easier to add new types of objects without having to modify the existing code.

To implement the factory design pattern, you would first define an interface or abstract class that specifies the methods that all objects created by the factory should implement. Then, you would create one or more concrete classes that implement the interface or extend the abstract class. Finally, you would create the factory class, which has a method for creating objects. This method takes in some information about the object to be created, and uses that information to determine which concrete class to instantiate and return.

Here is an example of how the factory design pattern might be implemented in Java:

public interface Shape {
  void draw();
}

public class Rectangle implements Shape {
  @Override
  public void draw() {
    // code to draw a rectangle
  }
}

public class Circle implements Shape {
  @Override
  public void draw() {
    // code to draw a circle
  }
}

public class ShapeFactory {
  public Shape getShape(String shapeType) {
    if (shapeType == null) {
      return null;
    }
    if (shapeType.equalsIgnoreCase("RECTANGLE")) {
      return new Rectangle();
    } else if (shapeType.equalsIgnoreCase("CIRCLE")) {
      return new Circle();
    }
    return null;
  }
}

Factory & Factory method & Abstract Factory

The factory pattern, factory method pattern, and abstract factory pattern are all creational design patterns that provide a way to create objects without specifying the exact class of object that will be created. These patterns are often used together to provide a flexible and modular way to create objects in a system.

Simple Factory

The factory pattern is a simple pattern that involves a factory class with a method for creating objects. This method takes in some information about the object to be created, and uses that information to determine which concrete class to instantiate and return. The factory pattern is useful when you need to create objects of different types, but the specific type is not known until runtime. Like above example.

Factory method

The factory method pattern is similar to the factory pattern, but it involves having a separate factory method for each type of object that can be created. This allows for more flexibility, as each factory method can be customized to create its specific type of object in the way that is most appropriate.

The factory method pattern is useful when you have a system that needs to be able to create objects of different types, but you want to have more control over the creation process for each type of object. This allows for more flexibility and modularity in the system, and makes it easier to add new types of objects without having to modify the existing code.

public interface Shape {
  void draw();
}

public class Rectangle implements Shape {
  @Override
  public void draw() {
    // code to draw a rectangle
  }
}

public class Circle implements Shape {
  @Override
  public void draw() {
    // code to draw a circle
  }
}

public abstract class ShapeFactory {
  public abstract Shape createShape();
}

public class RectangleFactory extends ShapeFactory {
  @Override
  public Shape createShape() {
    return new Rectangle();
  }
}

public class CircleFactory extends ShapeFactory {
  @Override
  public Shape createShape() {
    return new Circle();
  }
}

Abstract Factory

The abstract factory pattern is a design pattern that is used to create groups of related or dependent objects. This pattern is similar to the factory method pattern, but it provides a higher level of abstraction by providing a way to create a whole family of related objects, rather than just a single object.

The abstract factory pattern is often used in applications that need to work with multiple types of objects that belong to the same family. For example, an application that needs to work with different types of databases (such as MySQL, Oracle, and SQL Server) could use the abstract factory pattern to create a factory for each type of database. This would allow the application to create objects that are specific to the type of database that it is working with, without having to know the exact details of how those objects are created.

In general, the abstract factory pattern is useful when you need to create a set of related objects, but you want to decouple the objects from the specific details of their implementation. This allows you to create objects in a way that is flexible and extensible, and it makes it easier to change the implementation of those objects without affecting the rest of the application.

// Abstract base class for database factories
public abstract class DatabaseFactory {
  public abstract DatabaseConnection createConnection();
  public abstract DatabaseCommand createCommand();
  public abstract DatabaseParameter createParameter();
}

// Concrete factory for MySQL databases
public class MySqlDatabaseFactory extends DatabaseFactory {
  public DatabaseConnection createConnection() {
    return new MySqlDatabaseConnection();
  }

  public DatabaseCommand createCommand() {
    return new MySqlDatabaseCommand();
  }

  public DatabaseParameter createParameter() {
    return new MySqlDatabaseParameter();
  }
}

// Concrete factory for Oracle databases
public class OracleDatabaseFactory extends DatabaseFactory {
  public DatabaseConnection createConnection() {
    return new OracleDatabaseConnection();
  }

  public DatabaseCommand createCommand() {
    return new OracleDatabaseCommand();
  }

  public DatabaseParameter createParameter() {
    return new OracleDatabaseParameter();
  }
}

// Concrete classes for database objects
public class MySqlDatabaseConnection implements DatabaseConnection { ... }
public class MySqlDatabaseCommand implements DatabaseCommand { ... }
public class MySqlDatabaseParameter implements DatabaseParameter { ... }

public class OracleDatabaseConnection implements DatabaseConnection { ... }
public class OracleDatabaseCommand implements DatabaseCommand { ... }
public class OracleDatabaseParameter implements DatabaseParameter { ... }

Adopter

The adapter design pattern is a software design pattern that allows two incompatible classes to work together by converting the interface of one class into that of the other. This pattern is often used when working with legacy code, or code that has been written in a different programming language. The adapter acts as a bridge between the two classes, allowing them to communicate and collaborate.

When should we use Adapter pattern?

The adapter pattern is typically used in the following situations:

  1. When you want to use an existing class, but its interface is not compatible with the rest of your code.

  2. When you want to create a reusable class that cooperates with classes that don't have compatible interfaces.

  3. When you want to avoid writing a lot of code that converts data from one format to another.

Decorator Design pattern

The Decorator design pattern is a structural design pattern that allows you to add new behaviors to existing objects dynamically. It involves creating a wrapper class that encapsulates the original class and adds new behavior to it.

Here is an example of a decorator in Java:

public interface Car {
    void assemble();
}

public class BasicCar implements Car {
    @Override
    public void assemble() {
        System.out.println("Assembling basic car");
    }
}

public abstract class CarDecorator implements Car {
    protected Car car;

    public CarDecorator(Car car) {
        this.car = car;
    }

    @Override
    public void assemble() {
        this.car.assemble();
    }
}

public class SportsCar extends CarDecorator {
    public SportsCar(Car car) {
        super(car);
    }

    @Override
    public void assemble() {
        super.assemble();
        System.out.println("Adding sports car features");
    }
}

public class LuxuryCar extends CarDecorator {
    public LuxuryCar(Car car) {
        super(car);
    }

    @Override
    public void assemble() {
        super.assemble();
        System.out.println("Adding luxury car features");
    }
}

public class DecoratorPatternDemo {
    public static void main(String[] args) {
        Car sportsCar = new SportsCar(new BasicCar());
        sportsCar.assemble();
        System.out.println("\n*****");

        Car luxurySportsCar = new SportsCar(new LuxuryCar(new BasicCar()));
        luxurySportsCar.assemble();
    }
}

Output:

Assembling basic car
Adding sports car features

*****
Assembling basic car
Adding luxury car features
Adding sports car features

The decorator pattern is useful in situations where you want to add new behavior to an existing object dynamically, without changing the interface of the object. Some common use cases for the decorator pattern include:

  1. Adding new features to an existing object without changing its implementation.

  2. Dynamically adding and removing responsibilities to an object at runtime.

  3. Creating a flexible alternative to subclassing for extending the functionality of an object.

For example, you might use the decorator pattern to add logging or debugging functionality to an existing object without changing its implementation. You could also use it to add new features to a GUI widget, such as a scrollbar or a dropdown list, without changing the interface of the widget.

The decorator pattern is often used in conjunction with the factory pattern to create objects with a flexible and extensible design. It can also be used in conjunction with the adapter pattern to allow objects with incompatible interfaces to work together.

Decorator vs Adapter

Adapter changes the interface of an existing object, while Decorator enhances an object without changing its interface.

Adapter provides a different interface to the wrapped object, Proxy provides it with the same interface, and Decorator provides it with an enhanced interface.

Flyweight pattern

The flyweight pattern is a design pattern that allows you to minimize the use of memory by sharing data that is common to multiple objects. It involves creating a class that acts as a factory for a set of similar objects, and a separate class that represents the shared data.

The flyweight pattern is useful when you need to create a large number of objects that have a lot of common data. Instead of storing the common data in each object, you can use the flyweight pattern to store the data in a separate object that can be shared by multiple objects. This can significantly reduce the memory overhead of your program.

Keep in mind that the Flyweight Pattern is an optimization, and before applying it, make sure your program does have the RAM consumption problem related to having a massive number of similar objects in memory at the same time.

Observer

The observer pattern is a design pattern that defines a one-to-many dependency between objects, such that when one object (the subject) changes state, all of its dependents (the observers) are notified and updated automatically. The observer pattern is also known as the publish-subscribe pattern.

It allows you to change or take action on a set of objects when and if the state of another object changes.

You can introduce new subscriber classes without having to change the publisher's code, and vice versa if there's a publisher interface.

Here is a simple example of the observer pattern in Java:

import java.util.ArrayList;
import java.util.List;

// Subject interface
interface Subject {
  void registerObserver(Observer o);
  void removeObserver(Observer o);
  void notifyObservers();
}

// Concrete subject
class StockMarket implements Subject {
  private List<Observer> observers = new ArrayList<>();
  private double ibmPrice;
  private double aaplPrice;
  private double googPrice;

  public void setIbmPrice(double ibmPrice) {
    this.ibmPrice = ibmPrice;
    notifyObservers();
  }

  public void setAaplPrice(double aaplPrice) {
    this.aaplPrice = aaplPrice;
    notifyObservers();
  }

  public void setGoogPrice(double googPrice) {
    this.googPrice = googPrice;
    notifyObservers();
  }

  @Override
  public void registerObserver(Observer o) {
    observers.add(o);
  }

  @Override
  public void removeObserver(Observer o) {
    observers.remove(o);
  }

  @Override
  public void notifyObservers() {
    for (Observer o : observers) {
      o.update(ibmPrice, aaplPrice, googPrice);
    }
  }
}

// Observer interface
interface Observer {
  void update(double ibmPrice, double aaplPrice, double googPrice);
}

// Concrete observer
class StockPriceObserver implements Observer {
  private double ibmPrice;
  private double aaplPrice;
  private double googPrice;

  @Override
  public void update(double ibmPrice, double aaplPrice, double googPrice) {
    this.ibmPrice = ibmPrice;
    this.aaplPrice = aaplPrice;
    this.googPrice = googPrice;
    display();
  }

  public void display() {
    System.out.println("IBM: " + ibmPrice + " AAPL: " + aaplPrice + " GOOG: " + googPrice);
  }
}

// Client
class StockMarketClient {
  public static void main(String[] args) {
    StockMarket market = new StockMarket();
    StockPriceObserver observer1 = new StockPriceObserver();
    StockPriceObserver observer2 = new StockPriceObserver();

    market.registerObserver(observer1);
    market.registerObserver(observer2);

    market.setIbmPrice(197.00);
    market.setAaplPrice(677.60);
    market.setGoogPrice(676.40);
  }
}

Mediator - Behavioral

The mediator design pattern is a behavioral design pattern that allows you to define a mediator object that encapsulates the interactions between a set of objects, reducing the dependencies between them. It allows you to create a decoupled communication channel between objects, so that they can interact without being aware of each other's implementation details.

The mediator pattern is useful when you have a group of objects that need to communicate with each other, but you want to reduce the complexity of the communication by centralizing it in a single object. This can make it easier to maintain the system and make changes to it, as you only need to modify the mediator object instead of modifying all of the objects that communicate with it.

It restricts direct communications between objects and forces them to collaborate via a mediator, hence reducing the dependencies between them.

Here is an example of the mediator pattern in PHP:

interface Mediator {
    public function send($message, Colleague $colleague);
}

abstract class Colleague {
    protected $mediator;

    public function __construct(Mediator $mediator) {
        $this->mediator = $mediator;
    }
}

class ConcreteColleague extends Colleague {
    private $name;

    public function __construct(Mediator $mediator, $name) {
        parent::__construct($mediator);
        $this->name = $name;
    }

    public function send($message) {
        $this->mediator->send($message, $this);
    }

    public function receive($message) {
        echo $this->name . " receives message: " . $message . "\n";
    }
}

class ConcreteMediator implements Mediator {
    private $colleagues;

    public function __construct() {
        $this->colleagues = array();
    }

    public function addColleague(Colleague $colleague) {
        $this->colleagues[] = $colleague;
    }

    public function send($message, Colleague $colleague) {
        foreach ($this->colleagues as $c) {
            if ($c !== $colleague) {
                $c->receive($message);
            }
        }
    }
}

$mediator = new ConcreteMediator();

$colleague1 = new ConcreteColleague($mediator, "Colleague 1");
$colleague2 = new ConcreteColleague($mediator, "Colleague 2");
$colleague3 = new ConcreteColleague($mediator, "Colleague 3");

$mediator->addColleague($colleague1);
$mediator->addColleague($colleague2);
$mediator->addColleague($colleague3);

$colleague1->send("Hello, colleagues!");

Output:

Colleague 2 receives message: Hello, colleagues!
Colleague 3 receives message: Hello, colleagues!

Use cases:
The mediator pattern is useful in the following situations:

  1. When you have a group of objects that need to communicate with each other, but you want to reduce the complexity of the communication by centralizing it in a single object.

  2. When you want to change the communication between objects dynamically, without changing their implementation.

  3. When you want to decouple the communication between objects, so that they can interact without being aware of each other's implementation details.

  4. When you want to create a reusable communication system that can be used in different contexts.

  5. When you want to create a communication system that is flexible and easy to maintain.

Mediator vs Observer

  1. The mediator pattern defines a mediator object that encapsulates the interactions between a set of objects, reducing the dependencies between them. The observer pattern defines a one-to-many relationship between a subject and its observers, where the subject broadcasts events to its observers.

  2. In the mediator pattern, the communication is centralized in the mediator object, while in the observer pattern, the communication is decentralized, with the subject broadcasting events to its observers.

  3. The mediator pattern is useful when you want to create a decoupled communication channel between objects, while the observer pattern is useful when you want to create a dynamic one-to-many relationship between objects.

  4. The mediator pattern can make it easier to maintain the system and make changes to it, as you only need to modify the mediator object instead of modifying all of the objects that communicate with it. The observer pattern can make it easier to add or remove observers from the system, as you only need to modify the subject object.

Prototype - creational

The prototype pattern is a creational design pattern that allows you to create new objects by copying existing objects, rather than creating them from scratch. It can be used to improve performance and reduce the complexity of creating new objects, as you can reuse existing objects and modify them as needed, instead of creating new objects from scratch.
When creating a new example of an object is expensive for us, for example, if the object has many dependencies and complicated configuration, we use the prototype pattern.

Command

I will add Chain of Responsibility, Composite later