PHP Design Patterns: Singleton, Factory, etc.

Table of Contents

  • Introduction to Design Patterns
    • What are Design Patterns?
    • Why Use Design Patterns?
  • Common PHP Design Patterns
    • Singleton Pattern
    • Factory Pattern
    • Strategy Pattern
    • Observer Pattern
    • Adapter Pattern
  • Best Practices for Using Design Patterns in PHP
    • Choosing the Right Pattern
    • Avoiding Overuse
  • Practical Example: Using Multiple Design Patterns in a PHP Application
  • Summary

Introduction to Design Patterns

What are Design Patterns?

Design patterns are reusable solutions to common problems in software design. They are best practices, or templates, that software developers can use to solve common issues faced during application development. Rather than reinventing the wheel each time you face a problem, design patterns provide proven solutions that you can implement directly into your code.

There are many types of design patterns, but in this module, we will focus on some of the most commonly used patterns in PHP development, including the Singleton, Factory, Strategy, Observer, and Adapter patterns.

Why Use Design Patterns?

Design patterns help improve the scalability, maintainability, and flexibility of your code. By implementing the right design pattern for a given problem, you can:

  • Increase code reusability.
  • Improve code readability.
  • Reduce complexity by abstracting solutions to common problems.
  • Make your code more modular, which makes it easier to maintain and extend.

Common PHP Design Patterns

Let’s dive into some of the most commonly used design patterns in PHP.

1. Singleton Pattern

The Singleton Pattern ensures that a class has only one instance and provides a global point of access to that instance. This pattern is useful when you need to limit the number of objects created from a class, such as database connections or logging systems.

Implementation of Singleton Pattern
<?php
class Singleton {
private static $instance = null;

private function __construct() {
// Private constructor to prevent instantiation
}

public static function getInstance() {
if (self::$instance === null) {
self::$instance = new Singleton();
}
return self::$instance;
}

public function displayMessage() {
echo "Singleton Pattern: This is the only instance.";
}
}

// Usage
$singleton1 = Singleton::getInstance();
$singleton1->displayMessage();

$singleton2 = Singleton::getInstance();
echo ($singleton1 === $singleton2) ? ' Same instance' : ' Different instance';
?>
Explanation:
  • The constructor is private to prevent direct instantiation of the class.
  • The getInstance() method checks if the class instance already exists; if not, it creates one.
  • It guarantees that there will only ever be one instance of the class.

2. Factory Pattern

The Factory Pattern provides an interface for creating objects without specifying the exact class of the object that will be created. It’s useful when your application needs to create objects dynamically, depending on various factors such as user input or environment conditions.

Implementation of Factory Pattern
<?php
interface Product {
public function getName();
}

class ConcreteProductA implements Product {
public function getName() {
return "Product A";
}
}

class ConcreteProductB implements Product {
public function getName() {
return "Product B";
}
}

class ProductFactory {
public static function createProduct($type) {
if ($type == 'A') {
return new ConcreteProductA();
} elseif ($type == 'B') {
return new ConcreteProductB();
}
throw new Exception("Unknown product type.");
}
}

// Usage
$productA = ProductFactory::createProduct('A');
echo $productA->getName(); // Outputs: Product A

$productB = ProductFactory::createProduct('B');
echo $productB->getName(); // Outputs: Product B
?>
Explanation:
  • The Product interface defines the method getName(), which is implemented by the concrete product classes.
  • The ProductFactory class creates instances of ConcreteProductA or ConcreteProductB depending on the input.
  • The Factory Pattern decouples the object creation logic from the rest of the application.

3. Strategy Pattern

The Strategy Pattern defines a family of algorithms and allows clients to choose an algorithm at runtime. This pattern is particularly useful when there are different ways to perform an operation, and you want to make the algorithm interchangeable.

Implementation of Strategy Pattern
<?php
interface Strategy {
public function execute($a, $b);
}

class AddStrategy implements Strategy {
public function execute($a, $b) {
return $a + $b;
}
}

class SubtractStrategy implements Strategy {
public function execute($a, $b) {
return $a - $b;
}
}

class Calculator {
private $strategy;

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

public function executeStrategy($a, $b) {
return $this->strategy->execute($a, $b);
}
}

// Usage
$calculatorAdd = new Calculator(new AddStrategy());
echo $calculatorAdd->executeStrategy(10, 5); // Outputs: 15

$calculatorSubtract = new Calculator(new SubtractStrategy());
echo $calculatorSubtract->executeStrategy(10, 5); // Outputs: 5
?>
Explanation:
  • The Strategy interface defines the execute() method, which is implemented by different concrete strategies (AddStrategy, SubtractStrategy).
  • The Calculator class uses the strategy pattern to dynamically choose the algorithm at runtime.

4. Observer Pattern

The Observer Pattern is used when one object (the subject) changes state, and all dependent objects (observers) need to be notified. It is commonly used in event-driven programming.

Implementation of Observer Pattern
<?php
interface Observer {
public function update($message);
}

class ConcreteObserver implements Observer {
private $name;

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

public function update($message) {
echo "$this->name received message: $message\n";
}
}

class Subject {
private $observers = [];

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

public function notifyObservers($message) {
foreach ($this->observers as $observer) {
$observer->update($message);
}
}
}

// Usage
$observer1 = new ConcreteObserver("Observer 1");
$observer2 = new ConcreteObserver("Observer 2");

$subject = new Subject();
$subject->addObserver($observer1);
$subject->addObserver($observer2);

$subject->notifyObservers("New update available!"); // Notifies all observers
?>
Explanation:
  • The Observer interface defines the update() method, which is called to notify observers of a change.
  • The Subject class manages the list of observers and notifies them of any changes.

5. Adapter Pattern

The Adapter Pattern allows you to adapt one interface to another, enabling classes with incompatible interfaces to work together.

Implementation of Adapter Pattern
<?php
interface Target {
public function request();
}

class Adaptee {
public function specificRequest() {
echo "Specific request called.\n";
}
}

class Adapter implements Target {
private $adaptee;

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

public function request() {
$this->adaptee->specificRequest();
}
}

// Usage
$adaptee = new Adaptee();
$adapter = new Adapter($adaptee);
$adapter->request(); // Outputs: Specific request called.
?>
Explanation:
  • The Target interface defines the request() method.
  • The Adaptee class has a specificRequest() method, which is incompatible with the Target interface.
  • The Adapter class adapts the Adaptee to the Target interface, allowing them to work together.

Best Practices for Using Design Patterns in PHP

  1. Choosing the Right Pattern: Not every problem requires a design pattern. Choose the pattern that best fits the problem at hand.
  2. Avoid Overuse: Design patterns can introduce unnecessary complexity if used excessively. Use them when they provide clear benefits, such as improving readability, maintainability, or flexibility.
  3. Keep It Simple: While design patterns can improve code, they can also make it more complicated. Use patterns judiciously to avoid over-engineering your solutions.

Practical Example: Using Multiple Design Patterns in a PHP Application

Imagine a PHP application that processes user data and allows users to choose between different actions (e.g., updating their profile or sending a message). You could use several design patterns, such as the Factory and Strategy patterns, to structure this application in a flexible way.


Summary

In this module, we explored PHP Design Patterns and how they can be applied to solve common software design problems. We covered the following patterns:

  • Singleton Pattern: Ensures only one instance of a class.
  • Factory Pattern: Creates objects dynamically without specifying the exact class.
  • Strategy Pattern: Allows for interchangeable algorithms.
  • Observer Pattern: Notifies dependent objects of changes.
  • Adapter Pattern: Adapts incompatible interfaces to work together.

By using these patterns, you can improve your PHP applications’ flexibility, maintainability, and scalability.