Design patterns: common solutions to recurring problems in software design

Design patterns are common solutions to recurring problems in software design. They provide templates and best practices for structuring code to enhance code quality, maintainability, and reusability. Here are some of the most common design patterns in programming:

  1. Singleton Pattern: Ensures that a class has only one instance and provides a global point of access to it. Useful for creating a single point of control, such as a configuration manager or a logging service.
  2. Factory Method Pattern: Defines an interface for creating an object but allows subclasses to alter the type of objects that will be created. It’s often used in libraries and frameworks to create objects without specifying the exact class of object that will be created.
  3. Abstract Factory Pattern: Provides an interface for creating families of related or dependent objects without specifying their concrete classes. It’s useful when you need to ensure that the created objects are compatible with each other.
  4. Builder Pattern: Separates the construction of a complex object from its representation, allowing the same construction process to create different representations. Commonly used for creating complex objects like HTML or XML parsers.
  5. Prototype Pattern: Allows you to create new objects by copying an existing object, known as the prototype. It’s useful when the cost of creating an object is more expensive than copying an existing one.
  6. Adapter Pattern: Allows the interface of an existing class to be used as another interface. It’s often used to make existing classes work with others without modifying their source code.
  7. Decorator Pattern: Allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. It’s useful for extending the functionality of classes in a flexible way.
  8. 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. It’s commonly used in implementing distributed event handling systems and GUI frameworks.
  9. Strategy Pattern: Defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows the client to choose the appropriate algorithm at runtime.
  10. Command Pattern: Encapsulates a request as an object, thereby allowing for parameterization of clients with queuing, requests, and operations. It’s often used in implementing undo functionality or for abstracting the invocation of methods.
  11. Composite Pattern: Composes objects into tree structures to represent part-whole hierarchies. It allows clients to treat individual objects and compositions of objects uniformly.
  12. State Pattern: Allows an object to alter its behavior when its internal state changes. The object will appear to change its class.
  13. Template Method Pattern: Defines the skeleton of an algorithm in the superclass but lets subclasses override specific steps of the algorithm without changing its structure.
  14. Chain of Responsibility Pattern: Passes a request along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain.
  15. Visitor Pattern: Represents an operation to be performed on elements of an object structure. It lets you define a new operation without changing the classes of the elements on which it operates.

Singleton Pattern (#1) in PHP:

The Singleton Pattern ensures that a class has only one instance and provides a global point of access to that instance. Here’s an example of implementing a Singleton in PHP:

class Singleton {
    // Private static variable to hold the single instance of the class
    private static $instance = null;

    // Private constructor to prevent instantiation from outside the class
    private function __construct() {
    }

    // Public static method to get the single instance of the class
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    // Example method of the Singleton class
    public function showMessage() {
        echo "Hello from Singleton!";
    }
}

// Usage of the Singleton pattern
$singletonInstance1 = Singleton::getInstance();
$singletonInstance1->showMessage();

$singletonInstance2 = Singleton::getInstance();
$singletonInstance2->showMessage();

// Both instances are the same
if ($singletonInstance1 === $singletonInstance2) {
    echo "Singleton pattern works as expected. Both instances are the same.";
} else {
    echo "Singleton pattern failed. Instances are not the same.";
}

In this example, the Singleton class ensures that there is only one instance of itself by providing a static method getInstance() to access that instance. When you create multiple instances using getInstance(), they all refer to the same single instance of the Singleton class.

Factory Method Pattern (#2) in PHP:

The Factory Method Pattern defines an interface for creating objects, but it allows subclasses to alter the type of objects that will be created. Here’s an example:

// Abstract Product class
abstract class Product {
    abstract public function getName();
}

// Concrete Product class A
class ConcreteProductA extends Product {
    public function getName() {
        return "Product A";
    }
}

// Concrete Product class B
class ConcreteProductB extends Product {
    public function getName() {
        return "Product B";
    }
}

// Abstract Factory class
abstract class Factory {
    abstract public function createProduct();
}

// Concrete Factory for Product A
class ConcreteFactoryA extends Factory {
    public function createProduct() {
        return new ConcreteProductA();
    }
}

// Concrete Factory for Product B
class ConcreteFactoryB extends Factory {
    public function createProduct() {
        return new ConcreteProductB();
    }
}

// Client code
$factoryA = new ConcreteFactoryA();
$productA = $factoryA->createProduct();
echo "Product from Factory A: " . $productA->getName() . "\n";

$factoryB = new ConcreteFactoryB();
$productB = $factoryB->createProduct();
echo "Product from Factory B: " . $productB->getName() . "\n";

In this example, we have an abstract Product class with two concrete implementations (ConcreteProductA and ConcreteProductB). We also have an abstract Factory class with two concrete implementations (ConcreteFactoryA and ConcreteFactoryB), each of which creates a specific product.

The Factory Method Pattern allows you to create products (in this case, ProductA and ProductB) without specifying their concrete classes explicitly. Depending on the factory you use (ConcreteFactoryA or ConcreteFactoryB), you get the corresponding product (ProductA or ProductB). This promotes flexibility and extensibility in your code.

Abstract Factory Pattern (#3) in PHP:

The Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. Here’s an example:

// Abstract Product A
abstract class AbstractProductA {
    abstract public function getName();
}

// Concrete Product A1
class ConcreteProductA1 extends AbstractProductA {
    public function getName() {
        return "Product A1";
    }
}

// Concrete Product A2
class ConcreteProductA2 extends AbstractProductA {
    public function getName() {
        return "Product A2";
    }
}

// Abstract Product B
abstract class AbstractProductB {
    abstract public function getDescription();
}

// Concrete Product B1
class ConcreteProductB1 extends AbstractProductB {
    public function getDescription() {
        return "Product B1";
    }
}

// Concrete Product B2
class ConcreteProductB2 extends AbstractProductB {
    public function getDescription() {
        return "Product B2";
    }
}

// Abstract Factory
abstract class AbstractFactory {
    abstract public function createProductA();
    abstract public function createProductB();
}

// Concrete Factory 1
class ConcreteFactory1 extends AbstractFactory {
    public function createProductA() {
        return new ConcreteProductA1();
    }

    public function createProductB() {
        return new ConcreteProductB1();
    }
}

// Concrete Factory 2
class ConcreteFactory2 extends AbstractFactory {
    public function createProductA() {
        return new ConcreteProductA2();
    }

    public function createProductB() {
        return new ConcreteProductB2();
    }
}

// Client code
$factory1 = new ConcreteFactory1();
$productA1 = $factory1->createProductA();
$productB1 = $factory1->createProductB();

echo "Product A from Factory 1: " . $productA1->getName() . "\n";
echo "Product B from Factory 1: " . $productB1->getDescription() . "\n";

$factory2 = new ConcreteFactory2();
$productA2 = $factory2->createProductA();
$productB2 = $factory2->createProductB();

echo "Product A from Factory 2: " . $productA2->getName() . "\n";
echo "Product B from Factory 2: " . $productB2->getDescription() . "\n";

In this example, we have two abstract product families: AbstractProductA and AbstractProductB, each with two concrete implementations (ConcreteProductA1, ConcreteProductA2, ConcreteProductB1, and ConcreteProductB2). We also have two concrete factories (ConcreteFactory1 and ConcreteFactory2), each capable of creating products from one of these families.

The Abstract Factory Pattern allows you to create families of related objects (Product A and Product B) without specifying their concrete classes explicitly. Depending on the factory you use (ConcreteFactory1 or ConcreteFactory2), you get products from the corresponding family. This promotes the creation of consistent and compatible sets of objects.

Builder Pattern (#4) in PHP:

The Builder Pattern separates the construction of a complex object from its representation, allowing the same construction process to create different representations. Here’s an example:

// Product
class Pizza {
    private $size;
    private $cheese;
    private $pepperoni;
    private $bacon;

    public function __construct() {
        $this->size = "";
        $this->cheese = false;
        $this->pepperoni = false;
        $this->bacon = false;
    }

    public function setSize($size) {
        $this->size = $size;
    }

    public function addCheese() {
        $this->cheese = true;
    }

    public function addPepperoni() {
        $this->pepperoni = true;
    }

    public function addBacon() {
        $this->bacon = true;
    }

    public function getDescription() {
        $description = "Size: " . $this->size;
        $description .= "\nCheese: " . ($this->cheese ? "Yes" : "No");
        $description .= "\nPepperoni: " . ($this->pepperoni ? "Yes" : "No");
        $description .= "\nBacon: " . ($this->bacon ? "Yes" : "No");
        return $description;
    }
}

// Builder interface
interface PizzaBuilder {
    public function setSize($size);
    public function addCheese();
    public function addPepperoni();
    public function addBacon();
    public function build();
}

// Concrete builder
class DeluxePizzaBuilder implements PizzaBuilder {
    private $pizza;

    public function __construct() {
        $this->pizza = new Pizza();
    }

    public function setSize($size) {
        $this->pizza->setSize($size);
    }

    public function addCheese() {
        $this->pizza->addCheese();
    }

    public function addPepperoni() {
        $this->pizza->addPepperoni();
    }

    public function addBacon() {
        $this->pizza->addBacon();
    }

    public function build() {
        return $this->pizza;
    }
}

// Director
class PizzaDirector {
    public function buildDeluxePizza(PizzaBuilder $builder) {
        $builder->setSize("Large");
        $builder->addCheese();
        $builder->addPepperoni();
        $builder->addBacon();
    }
}

// Client code
$director = new PizzaDirector();
$deluxePizzaBuilder = new DeluxePizzaBuilder();
$director->buildDeluxePizza($deluxePizzaBuilder);
$deluxePizza = $deluxePizzaBuilder->build();

echo "Deluxe Pizza:\n";
echo $deluxePizza->getDescription();

In this example, we have a Pizza class representing a complex object (a pizza) with various attributes like size, cheese, pepperoni, and bacon. The PizzaBuilder interface defines the methods for building a pizza. The DeluxePizzaBuilder is a concrete builder that implements the PizzaBuilder interface to construct a specific type of pizza.

The PizzaDirector class is responsible for directing the construction process, and it uses the builder to build a deluxe pizza. Finally, the client code demonstrates how to create a deluxe pizza using the builder pattern. This pattern allows you to create complex objects step by step while keeping the construction logic separate from the product itself.

Prototype Pattern (#5) in PHP:

The Prototype Pattern allows you to create new objects by copying an existing object, known as the prototype. Here’s an example:

// Prototype interface
interface Prototype {
    public function clone();
}

// Concrete Prototype
class ConcretePrototype implements Prototype {
    private $property1;
    private $property2;

    public function __construct($property1, $property2) {
        $this->property1 = $property1;
        $this->property2 = $property2;
    }

    public function clone() {
        // Create a new instance and copy the current object's properties
        return new ConcretePrototype($this->property1, $this->property2);
    }

    public function getProperty1() {
        return $this->property1;
    }

    public function getProperty2() {
        return $this->property2;
    }
}

// Client code
$originalPrototype = new ConcretePrototype("Property 1 Value", "Property 2 Value");

// Clone the prototype to create a new object
$clonedPrototype = $originalPrototype->clone();

// You now have two separate objects with the same properties
echo "Original Prototype - Property 1: " . $originalPrototype->getProperty1() . "\n";
echo "Original Prototype - Property 2: " . $originalPrototype->getProperty2() . "\n";

echo "Cloned Prototype - Property 1: " . $clonedPrototype->getProperty1() . "\n";
echo "Cloned Prototype - Property 2: " . $clonedPrototype->getProperty2() . "\n";

In this example, we have a Prototype interface that defines a clone() method. The ConcretePrototype class implements this interface and provides a method to create a new instance by copying its properties.

The client code creates an original prototype and then clones it to create a new object. Both the original and the cloned objects have the same properties, but they are separate instances. The Prototype Pattern is useful when you want to create new objects with the same initial state as an existing object, typically to avoid the overhead of creating objects from scratch.

Adapter Pattern (#6) in PHP:

The Adapter Pattern allows the interface of an existing class to be used as another interface. It’s often used to make existing classes work with others without modifying their source code. Here’s an example:

// Existing class with a specific interface
class Adaptee {
    public function specificRequest() {
        return "Adaptee's specific request";
    }
}

// Target interface that the client expects
interface Target {
    public function request();
}

// Adapter class that adapts Adaptee to the Target interface
class Adapter implements Target {
    private $adaptee;

    public function __construct(Adaptee $adaptee) {
        $this->adaptee = $adaptee;
    }

    public function request() {
        return "Adapter: " . $this->adaptee->specificRequest();
    }
}

// Client code
$adaptee = new Adaptee();
$adapter = new Adapter($adaptee);

echo "Client expects a Target interface:\n";
echo "Target: " . $adapter->request() . "\n";

In this example:

  1. Adaptee is an existing class with a specific interface (in this case, it has a specificRequest method).
  2. Target is the interface that the client expects, and it defines a request method.
  3. Adapter is a class that adapts the Adaptee to the Target interface by composing an instance of Adaptee and implementing the request method, which delegates to the specificRequest method of Adaptee.
  4. The client code creates an instance of Adaptee, then creates an Adapter object that adapts the Adaptee to the Target interface. When the client calls request on the Adapter, it internally calls specificRequest on the Adaptee and provides the expected interface.

The Adapter Pattern allows you to use existing classes in new contexts without modifying their source code, making it a useful pattern for integrating third-party or legacy code into your application.

Decorator Pattern (#7) in PHP:

The Decorator Pattern allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. Here’s an example:

// Component interface
interface Coffee {
    public function cost();
}

// Concrete Component
class SimpleCoffee implements Coffee {
    public function cost() {
        return 5; // Base cost of simple coffee
    }
}

// Decorator abstract class
abstract class CoffeeDecorator implements Coffee {
    protected $coffee;

    public function __construct(Coffee $coffee) {
        $this->coffee = $coffee;
    }

    public function cost() {
        return $this->coffee->cost();
    }
}

// Concrete Decorator 1: Milk
class MilkDecorator extends CoffeeDecorator {
    public function cost() {
        return parent::cost() + 2; // Additional cost for milk
    }
}

// Concrete Decorator 2: Sugar
class SugarDecorator extends CoffeeDecorator {
    public function cost() {
        return parent::cost() + 1; // Additional cost for sugar
    }
}

// Client code
$coffee = new SimpleCoffee();
echo "Cost of Simple Coffee: $" . $coffee->cost() . "\n";

$milkCoffee = new MilkDecorator($coffee);
echo "Cost of Coffee with Milk: $" . $milkCoffee->cost() . "\n";

$sugarMilkCoffee = new SugarDecorator($milkCoffee);
echo "Cost of Coffee with Milk and Sugar: $" . $sugarMilkCoffee->cost() . "\n";

In this example:

  • Coffee is the interface representing a component (e.g., a simple coffee).
  • SimpleCoffee is a concrete component that implements the Coffee interface with a base cost.
  • CoffeeDecorator is an abstract decorator class that also implements the Coffee interface and has a reference to a Coffee object.
  • MilkDecorator and SugarDecorator are concrete decorator classes that extend CoffeeDecorator and add specific functionalities (costs for milk and sugar, respectively) to the wrapped Coffee object.
  • The client code demonstrates how to create and decorate coffee objects. It starts with a simple coffee, then adds milk and sugar decorators to calculate the final cost.

The Decorator Pattern allows you to add new features or responsibilities to objects dynamically without altering their structure. This promotes flexibility and is often used in scenarios where you want to avoid creating a large number of subclasses to represent different combinations of features.

Observer Pattern (#8) in PHP:

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. Here’s an example:

// Subject interface (Observable)
interface Subject {
    public function addObserver(Observer $observer);
    public function removeObserver(Observer $observer);
    public function notifyObservers();
}

// Concrete Subject
class WeatherData implements Subject {
    private $temperature;
    private $humidity;
    private $pressure;
    private $observers = [];

    public function addObserver(Observer $observer) {
        $this->observers[] = $observer;
    }

    public function removeObserver(Observer $observer) {
        $index = array_search($observer, $this->observers);
        if ($index !== false) {
            unset($this->observers[$index]);
        }
    }

    public function notifyObservers() {
        foreach ($this->observers as $observer) {
            $observer->update($this->temperature, $this->humidity, $this->pressure);
        }
    }

    public function setMeasurements($temperature, $humidity, $pressure) {
        $this->temperature = $temperature;
        $this->humidity = $humidity;
        $this->pressure = $pressure;
        $this->notifyObservers();
    }
}

// Observer interface
interface Observer {
    public function update($temperature, $humidity, $pressure);
}

// Concrete Observer 1
class CurrentConditionsDisplay implements Observer {
    public function update($temperature, $humidity, $pressure) {
        echo "Current Conditions: Temperature = $temperature┬░C, Humidity = $humidity%, Pressure = $pressure hPa\n";
    }
}

// Concrete Observer 2
class StatisticsDisplay implements Observer {
    public function update($temperature, $humidity, $pressure) {
        echo "Statistics: Some statistics based on temperature, humidity, and pressure\n";
    }
}

// Client code
$weatherData = new WeatherData();

$currentConditionsDisplay = new CurrentConditionsDisplay();
$statisticsDisplay = new StatisticsDisplay();

$weatherData->addObserver($currentConditionsDisplay);
$weatherData->addObserver($statisticsDisplay);

$weatherData->setMeasurements(25, 65, 1013);
$weatherData->setMeasurements(28, 70, 1012);

In this example:

  • Subject is the interface representing the subject (observable) that maintains a list of observers and notifies them when its state changes.
  • WeatherData is a concrete subject that implements the Subject interface. It keeps track of temperature, humidity, and pressure and notifies its observers when these values change.
  • Observer is the interface representing the observers. They implement the update method to receive updates from the subject.
  • CurrentConditionsDisplay and StatisticsDisplay are concrete observers that implement the Observer interface. They define how they react to updates from the WeatherData.
  • The client code sets up the weather data object, adds observers (current conditions display and statistics display), and updates the weather data with new measurements. When the weather data changes, it automatically notifies all registered observers, which in turn respond to the changes.

The Observer Pattern helps maintain loose coupling between subjects and observers, making it a powerful tool for implementing event handling and notification systems in software.

Strategy Pattern (#9) in PHP:

The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows the client to choose the appropriate algorithm at runtime. Here’s an example:

// Strategy interface
interface PaymentStrategy {
    public function pay($amount);
}

// Concrete Strategy 1: Credit Card Payment
class CreditCardPayment implements PaymentStrategy {
    private $cardNumber;
    private $cardHolder;

    public function __construct($cardNumber, $cardHolder) {
        $this->cardNumber = $cardNumber;
        $this->cardHolder = $cardHolder;
    }

    public function pay($amount) {
        echo "Paid $amount USD with Credit Card (Card Number: {$this->cardNumber}, Card Holder: {$this->cardHolder})\n";
    }
}

// Concrete Strategy 2: PayPal Payment
class PayPalPayment implements PaymentStrategy {
    private $email;

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

    public function pay($amount) {
        echo "Paid $amount USD with PayPal (Email: {$this->email})\n";
    }
}

// Context (Shopping Cart)
class ShoppingCart {
    private $paymentStrategy;

    public function setPaymentStrategy(PaymentStrategy $paymentStrategy) {
        $this->paymentStrategy = $paymentStrategy;
    }

    public function checkout($amount) {
        $this->paymentStrategy->pay($amount);
    }
}

// Client code
$cart = new ShoppingCart();

// Pay with Credit Card
$creditCardPayment = new CreditCardPayment("1234-5678-9012-3456", "John Doe");
$cart->setPaymentStrategy($creditCardPayment);
$cart->checkout(100);

// Pay with PayPal
$paypalPayment = new PayPalPayment("[email protected]");
$cart->setPaymentStrategy($paypalPayment);
$cart->checkout(50);

In this example:

  • PaymentStrategy is the interface representing payment strategies. Concrete strategies like CreditCardPayment and PayPalPayment implement this interface and provide their own payment logic.
  • The ShoppingCart class represents the context that uses the selected payment strategy. It has a setPaymentStrategy method to set the chosen strategy and a checkout method to perform the payment.
  • The client code creates instances of payment strategies (CreditCardPayment and PayPalPayment) and sets them in the ShoppingCart. Then, it performs checkouts with different payment methods.

The Strategy Pattern allows you to easily switch between different algorithms or strategies for a specific task (in this case, payment) without changing the client code. It promotes flexibility and maintainability in situations where you need to support multiple variations of a behavior.

Command Pattern (#10) in PHP:

The Command Pattern encapsulates a request as an object, allowing you to parameterize clients with queuing, requests, and operations. It’s often used to implement undo functionality or to decouple the sender and receiver of a request. Here’s an example:

// Receiver
class Light {
    public function on() {
        echo "Light is on\n";
    }

    public function off() {
        echo "Light is off\n";
    }
}

// Command interface
interface Command {
    public function execute();
}

// Concrete Command 1: Turn On Light
class TurnOnLightCommand implements Command {
    private $light;

    public function __construct(Light $light) {
        $this->light = $light;
    }

    public function execute() {
        $this->light->on();
    }
}

// Concrete Command 2: Turn Off Light
class TurnOffLightCommand implements Command {
    private $light;

    public function __construct(Light $light) {
        $this->light = $light;
    }

    public function execute() {
        $this->light->off();
    }
}

// Invoker
class RemoteControl {
    private $command;

    public function setCommand(Command $command) {
        $this->command = $command;
    }

    public function pressButton() {
        $this->command->execute();
    }
}

// Client code
$light = new Light();
$turnOnCommand = new TurnOnLightCommand($light);
$turnOffCommand = new TurnOffLightCommand($light);

$remoteControl = new RemoteControl();

$remoteControl->setCommand($turnOnCommand);
$remoteControl->pressButton();

$remoteControl->setCommand($turnOffCommand);
$remoteControl->pressButton();

In this example:

  • Light is the receiver that performs the actual actions (turning the light on and off).
  • Command is the interface representing commands. Concrete commands like TurnOnLightCommand and TurnOffLightCommand implement this interface and encapsulate the actions to be executed.
  • RemoteControl is the invoker that sets and triggers the commands. It has a setCommand method to set the command to be executed and a pressButton method to invoke the command.
  • The client code creates a Light object, concrete command objects (TurnOnLightCommand and TurnOffLightCommand), and the RemoteControl object. It sets commands and triggers them.

The Command Pattern allows you to decouple the sender (the invoker) and receiver (the receiver of the action) of a request, making it easier to extend and maintain the system. It also provides a way to store and manage a history of commands, which can be used for undo/redo functionality.

Composite Pattern (#11) in PHP:

The Composite Pattern composes objects into tree structures to represent part-whole hierarchies. It allows clients to treat individual objects and compositions of objects uniformly. Here’s an example:

// Component interface
interface Component {
    public function operation();
}

// Leaf component
class Leaf implements Component {
    private $name;

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

    public function operation() {
        echo "Leaf {$this->name} operation\n";
    }
}

// Composite component
class Composite implements Component {
    private $name;
    private $children = [];

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

    public function operation() {
        echo "Composite {$this->name} operation\n";
        foreach ($this->children as $child) {
            $child->operation();
        }
    }

    public function add(Component $component) {
        $this->children[] = $component;
    }

    public function remove(Component $component) {
        $index = array_search($component, $this->children);
        if ($index !== false) {
            unset($this->children[$index]);
        }
    }
}

// Client code
$leaf1 = new Leaf("1");
$leaf2 = new Leaf("2");
$leaf3 = new Leaf("3");

$composite1 = new Composite("X");
$composite1->add($leaf1);
$composite1->add($leaf2);

$composite2 = new Composite("Y");
$composite2->add($leaf3);

$rootComposite = new Composite("Root");
$rootComposite->add($composite1);
$rootComposite->add($composite2);

echo "Calling operation on individual leaf:\n";
$leaf1->operation();

echo "\nCalling operation on composite (root):\n";
$rootComposite->operation();

In this example:

  • Component is the interface representing both leaf and composite components. It declares the operation method that both leaf and composite components implement.
  • Leaf is a leaf component that represents individual objects in the composition.
  • Composite is a composite component that can hold other components (both leaf and composite) as its children. It implements the operation method by performing its own operation and delegating it to its children.
  • The client code creates instances of leaf and composite components, adds them to composites, and calls the operation method on individual leaf and composite objects.

The Composite Pattern allows you to build complex structures from simple objects while treating both individual objects and compositions of objects uniformly. It’s particularly useful for representing hierarchical structures, such as file systems or graphical user interface elements.

State Pattern (#12) in PHP:

The State Pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class. Here’s an example:

// Context class
class Context {
    private $state;

    public function setState(State $state) {
        $this->state = $state;
    }

    public function request() {
        $this->state->handle($this);
    }
}

// State interface
interface State {
    public function handle(Context $context);
}

// Concrete State 1
class ConcreteStateA implements State {
    public function handle(Context $context) {
        echo "Handling request in State A\n";
        $context->setState(new ConcreteStateB());
    }
}

// Concrete State 2
class ConcreteStateB implements State {
    public function handle(Context $context) {
        echo "Handling request in State B\n";
        $context->setState(new ConcreteStateA());
    }
}

// Client code
$context = new Context();
$stateA = new ConcreteStateA();
$stateB = new ConcreteStateB();

$context->setState($stateA);
$context->request();
$context->request();
$context->request();

In this example:

  • Context is the class that has a reference to the current state and delegates the handling of requests to the state objects.
  • State is the interface that declares a handle method, which concrete states implement.
  • ConcreteStateA and ConcreteStateB are concrete state classes that implement the State interface. Each state handles requests differently and may transition to a different state.
  • The client code creates a context object and sets its initial state to ConcreteStateA. It then makes multiple requests on the context, and the state changes between ConcreteStateA and ConcreteStateB as it handles requests.

The State Pattern allows you to model an object’s behavior in a way that it can switch between different states without changing its external interface. It promotes flexibility and simplifies the management of complex state-dependent behavior.

Chain of Responsibility Pattern (#13) in PHP:

The Chain of Responsibility Pattern is a behavioral design pattern that allows you to pass requests along a chain of handlers. Each handler decides either to process the request or to pass it to the next handler in the chain. Here’s an example:

// Handler interface
interface Handler {
    public function setNext(Handler $handler);
    public function handleRequest($request);
}

// Abstract Handler
abstract class AbstractHandler implements Handler {
    private $nextHandler;

    public function setNext(Handler $handler) {
        $this->nextHandler = $handler;
    }

    public function handleRequest($request) {
        if ($this->nextHandler !== null) {
            $this->nextHandler->handleRequest($request);
        }
    }
}

// Concrete Handler 1
class ConcreteHandlerA extends AbstractHandler {
    public function handleRequest($request) {
        if ($request === 'A') {
            echo "ConcreteHandlerA handled the request '$request'\n";
        } else {
            parent::handleRequest($request);
        }
    }
}

// Concrete Handler 2
class ConcreteHandlerB extends AbstractHandler {
    public function handleRequest($request) {
        if ($request === 'B') {
            echo "ConcreteHandlerB handled the request '$request'\n";
        } else {
            parent::handleRequest($request);
        }
    }
}

// Client code
$handlerA = new ConcreteHandlerA();
$handlerB = new ConcreteHandlerB();

$handlerA->setNext($handlerB);

$requests = ['A', 'B', 'C'];

foreach ($requests as $request) {
    echo "Sending request: '$request'\n";
    $handlerA->handleRequest($request);
}

In this example:

  • Handler is the interface for handling requests. It includes methods for setting the next handler in the chain and handling requests.
  • AbstractHandler is an abstract class that implements the Handler interface and provides default behavior for passing requests to the next handler in the chain.
  • ConcreteHandlerA and ConcreteHandlerB are concrete handler classes that extend AbstractHandler. Each handler checks if it can handle the request or passes it to the next handler in the chain.
  • The client code creates instances of ConcreteHandlerA and ConcreteHandlerB, sets up the chain of responsibility, and sends multiple requests to the chain. Each request is handled by the first handler that can process it.

The Chain of Responsibility Pattern allows you to create flexible and reusable chains of handlers to process requests in a systematic way. It helps avoid coupling between senders and receivers of requests and allows you to add or remove handlers without changing the client code.

Visitor Pattern (#14) in PHP:

The Visitor Pattern is a behavioral design pattern that allows you to add new behaviors to objects without altering their structure. It involves defining a visitor interface with visit methods for each element type and implementing concrete visitors to perform specific operations on elements. Here’s an example:

// Element interface
interface Element {
    public function accept(Visitor $visitor);
}

// Concrete Element 1
class ConcreteElementA implements Element {
    public function accept(Visitor $visitor) {
        $visitor->visitElementA($this);
    }

    public function operationA() {
        echo "Operation A on ConcreteElementA\n";
    }
}

// Concrete Element 2
class ConcreteElementB implements Element {
    public function accept(Visitor $visitor) {
        $visitor->visitElementB($this);
    }

    public function operationB() {
        echo "Operation B on ConcreteElementB\n";
    }
}

// Visitor interface
interface Visitor {
    public function visitElementA(ConcreteElementA $elementA);
    public function visitElementB(ConcreteElementB $elementB);
}

// Concrete Visitor
class ConcreteVisitor implements Visitor {
    public function visitElementA(ConcreteElementA $elementA) {
        echo "Visitor is performing operation on ConcreteElementA\n";
        $elementA->operationA();
    }

    public function visitElementB(ConcreteElementB $elementB) {
        echo "Visitor is performing operation on ConcreteElementB\n";
        $elementB->operationB();
    }
}

// Client code
$elementA = new ConcreteElementA();
$elementB = new ConcreteElementB();
$visitor = new ConcreteVisitor();

$elementA->accept($visitor);
$elementB->accept($visitor);

In this example:

  • Element is the interface for elements that can accept visitors. It includes an accept method that allows the element to call the appropriate visitor’s visit method.
  • ConcreteElementA and ConcreteElementB are concrete element classes that implement the Element interface. Each element provides its own implementation of the accept method and specific operations.
  • Visitor is the interface for visitors. It declares visit methods for each type of element. In this case, there are visitElementA and visitElementB methods.
  • ConcreteVisitor is a concrete visitor class that implements the Visitor interface. It provides implementations for each visit method and performs operations on the elements.
  • The client code creates instances of concrete elements (ConcreteElementA and ConcreteElementB) and a concrete visitor (ConcreteVisitor). It then calls the accept method on each element, passing the visitor, which performs the appropriate operations on the elements.

The Visitor Pattern allows you to separate the algorithms or operations from the elements’ structure, making it easier to add new operations without modifying the elements themselves. It is especially useful when you have a complex structure of elements and want to perform various operations on them.

Template Method Pattern (#15) in PHP:

The Template Method Pattern is a behavioral design pattern that defines the skeleton of an algorithm in a method but lets subclasses override specific steps of the algorithm without changing its structure. Here’s an example:

// Abstract Class with Template Method
abstract class AbstractClass {
    public function templateMethod() {
        $this->stepOne();
        $this->stepTwo();
        $this->stepThree();
    }

    abstract protected function stepOne();
    abstract protected function stepTwo();
    abstract protected function stepThree();
}

// Concrete Class 1
class ConcreteClass1 extends AbstractClass {
    protected function stepOne() {
        echo "ConcreteClass1: Step 1\n";
    }

    protected function stepTwo() {
        echo "ConcreteClass1: Step 2\n";
    }

    protected function stepThree() {
        echo "ConcreteClass1: Step 3\n";
    }
}

// Concrete Class 2
class ConcreteClass2 extends AbstractClass {
    protected function stepOne() {
        echo "ConcreteClass2: Step 1\n";
    }

    protected function stepTwo() {
        echo "ConcreteClass2: Step 2\n";
    }

    protected function stepThree() {
        echo "ConcreteClass2: Step 3\n";
    }
}

// Client code
$object1 = new ConcreteClass1();
$object2 = new ConcreteClass2();

echo "Object 1:\n";
$object1->templateMethod();

echo "\nObject 2:\n";
$object2->templateMethod();

In this example:

  • AbstractClass is an abstract class that defines the template method templateMethod(), which consists of three steps: stepOne(), stepTwo(), and stepThree(). These steps are declared as abstract methods, so concrete subclasses must implement them.
  • ConcreteClass1 and ConcreteClass2 are concrete subclasses that extend AbstractClass and provide their implementations for the abstract steps.
  • The client code creates instances of ConcreteClass1 and ConcreteClass2 and calls the templateMethod() on each object. The template method follows the defined algorithm structure but delegates the specific steps to the concrete subclasses.

When you run the code, you’ll see that the template method is the same for both ConcreteClass1 and ConcreteClass2, but the implementation of the individual steps varies, allowing for flexibility and customization in each subclass. This pattern is useful when you want to define a common algorithm structure but allow for variations in specific steps of the algorithm.

In popular PHP Frameworks like Laravel and Symfony

How some of the design patterns are used in popular PHP frameworks like Laravel and Symfony:

Singleton Pattern:

  • In Laravel, the Application container (Illuminate\Container\Container) is implemented as a Singleton. It provides a centralized location for managing and resolving dependencies throughout the application.

Factory Method Pattern:

  • Both Laravel and Symfony use the Factory Method Pattern extensively for creating instances of various components. For example, Laravel’s service container relies on factory methods to resolve and instantiate services.

Observer Pattern:

  • Laravel’s Event system uses the Observer Pattern. It allows you to subscribe to and listen for events and then trigger specific actions when those events occur. This promotes loose coupling and makes it easy to add and remove event listeners.

Decorator Pattern:

  • Middleware in Laravel and Filters in Symfony use the Decorator Pattern to wrap and add functionality to HTTP requests and responses. You can stack multiple middleware or filters to apply different layers of behavior to an HTTP request.

Command Pattern:

  • Laravel’s Artisan Console commands are a great example of the Command Pattern. Each console command is encapsulated as an object with an handle method, making it easy to create and execute custom commands.

Strategy Pattern:

  • Both Laravel and Symfony use the Strategy Pattern for various components. For example, Laravel’s authentication system allows you to use different authentication strategies, and Symfony’s form system provides strategies for rendering and processing forms.

Composite Pattern:

  • In Symfony, the Form Component uses the Composite Pattern to build complex forms from smaller, reusable form elements. It allows you to create form structures with nested elements.

State Pattern:

  • Laravel’s Authorization system, like Gates and Policies, can be seen as implementing the State Pattern. The authorization logic can change based on the current state and conditions.

Chain of Responsibility Pattern:

  • Middleware in Laravel and Event Listeners/Handlers in Symfony are examples of the Chain of Responsibility Pattern. Each middleware or event listener can handle a specific aspect of the request/response, and they are processed in a chain.

Template Method Pattern:

  • Laravel and Symfony provide base classes and abstract methods that developers can extend and implement in their applications. For example, in Laravel, you can extend Illuminate\Http\Controller to create custom controllers, following the Template Method Pattern.

These design patterns are used in frameworks to provide structure, flexibility, and extensibility. They enable developers to build applications efficiently by following best practices and established design principles. Frameworks like Laravel and Symfony abstract many of these patterns, making it easier for developers to focus on building robust and maintainable applications.

The usefulness and popularity of design patterns can vary depending on the specific context and the problem you’re trying to solve. However, some design patterns are more commonly used and considered highly valuable due to their versatility and applicability across various software development scenarios. Here are a few design patterns that are often considered the most useful and widely used:

  1. Singleton Pattern: The Singleton Pattern is widely used when you need to ensure that a class has only one instance and provide a global point of access to that instance. It’s frequently used for managing resources like database connections and application configuration.
  2. Factory Method Pattern: The Factory Method Pattern is essential for creating objects in a flexible and extensible way. It’s commonly used for dependency injection and managing object creation in frameworks and libraries.
  3. Observer Pattern: The Observer Pattern is prevalent in event-driven and reactive programming. It’s used in various frameworks and libraries to handle events and notifications, allowing different parts of an application to react to changes in state.
  4. Decorator Pattern: The Decorator Pattern is useful for adding behavior to objects dynamically. It’s commonly used in GUI frameworks for widgets and in web development for middleware and filters.
  5. Command Pattern: The Command Pattern is employed in many places, especially in command-line interfaces, undo/redo functionality, and queuing systems.
  6. Strategy Pattern: The Strategy Pattern is used for algorithmic flexibility. It’s commonly applied in areas like sorting, searching, and various algorithm implementations.
  7. Composite Pattern: The Composite Pattern is prevalent in GUI frameworks for building complex user interfaces from simple components. It’s also used in document structures, such as HTML or XML.
  8. Chain of Responsibility Pattern: The Chain of Responsibility Pattern is often seen in middleware implementations, event handling, and in scenarios where you need to pass a request through a series of handlers.
  9. Template Method Pattern: The Template Method Pattern is widely used in frameworks and libraries to define the overall structure of an algorithm while allowing subclasses to provide specific implementations for certain steps.

The actual usefulness of a design pattern depends on the specific requirements and architecture of a project. It’s essential to understand the principles and trade-offs of each pattern and apply them appropriately to solve specific design and architectural challenges.

Design patterns are valuable tools for software developers to solve common problems while adhering to best practices and principles like encapsulation, abstraction, and separation of concerns. The choice of which design pattern to use depends on the specific problem you’re trying to solve and the context of your application.