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:
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- Composite Pattern: Composes objects into tree structures to represent part-whole hierarchies. It allows clients to treat individual objects and compositions of objects uniformly.
- State Pattern: Allows an object to alter its behavior when its internal state changes. The object will appear to change its class.
- 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.
- 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.
- 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:
Adaptee
is an existing class with a specific interface (in this case, it has aspecificRequest
method).Target
is the interface that the client expects, and it defines arequest
method.Adapter
is a class that adapts theAdaptee
to theTarget
interface by composing an instance ofAdaptee
and implementing therequest
method, which delegates to thespecificRequest
method ofAdaptee
.- The client code creates an instance of
Adaptee
, then creates anAdapter
object that adapts theAdaptee
to theTarget
interface. When the client callsrequest
on theAdapter
, it internally callsspecificRequest
on theAdaptee
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 theCoffee
interface with a base cost.CoffeeDecorator
is an abstract decorator class that also implements theCoffee
interface and has a reference to aCoffee
object.MilkDecorator
andSugarDecorator
are concrete decorator classes that extendCoffeeDecorator
and add specific functionalities (costs for milk and sugar, respectively) to the wrappedCoffee
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 theSubject
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 theupdate
method to receive updates from the subject.CurrentConditionsDisplay
andStatisticsDisplay
are concrete observers that implement theObserver
interface. They define how they react to updates from theWeatherData
.- 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 likeCreditCardPayment
andPayPalPayment
implement this interface and provide their own payment logic.- The
ShoppingCart
class represents the context that uses the selected payment strategy. It has asetPaymentStrategy
method to set the chosen strategy and acheckout
method to perform the payment. - The client code creates instances of payment strategies (
CreditCardPayment
andPayPalPayment
) and sets them in theShoppingCart
. 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 likeTurnOnLightCommand
andTurnOffLightCommand
implement this interface and encapsulate the actions to be executed.RemoteControl
is the invoker that sets and triggers the commands. It has asetCommand
method to set the command to be executed and apressButton
method to invoke the command.- The client code creates a
Light
object, concrete command objects (TurnOnLightCommand
andTurnOffLightCommand
), and theRemoteControl
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 theoperation
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 theoperation
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 ahandle
method, which concrete states implement.ConcreteStateA
andConcreteStateB
are concrete state classes that implement theState
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 betweenConcreteStateA
andConcreteStateB
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 theHandler
interface and provides default behavior for passing requests to the next handler in the chain.ConcreteHandlerA
andConcreteHandlerB
are concrete handler classes that extendAbstractHandler
. 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
andConcreteHandlerB
, 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 anaccept
method that allows the element to call the appropriate visitor’s visit method.ConcreteElementA
andConcreteElementB
are concrete element classes that implement theElement
interface. Each element provides its own implementation of theaccept
method and specific operations.Visitor
is the interface for visitors. It declares visit methods for each type of element. In this case, there arevisitElementA
andvisitElementB
methods.ConcreteVisitor
is a concrete visitor class that implements theVisitor
interface. It provides implementations for each visit method and performs operations on the elements.- The client code creates instances of concrete elements (
ConcreteElementA
andConcreteElementB
) and a concrete visitor (ConcreteVisitor
). It then calls theaccept
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 methodtemplateMethod()
, which consists of three steps:stepOne()
,stepTwo()
, andstepThree()
. These steps are declared as abstract methods, so concrete subclasses must implement them.ConcreteClass1
andConcreteClass2
are concrete subclasses that extendAbstractClass
and provide their implementations for the abstract steps.- The client code creates instances of
ConcreteClass1
andConcreteClass2
and calls thetemplateMethod()
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:
- 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.
- 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.
- 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.
- 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.
- Command Pattern: The Command Pattern is employed in many places, especially in command-line interfaces, undo/redo functionality, and queuing systems.
- Strategy Pattern: The Strategy Pattern is used for algorithmic flexibility. It’s commonly applied in areas like sorting, searching, and various algorithm implementations.
- 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.
- 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.
- 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.