Category: Design Pattern

  • State design parttern

    The state pattern is useful when you need to change the behavior of an object based upon changes to its internal state. You can also use the pattern to simplify methods with long conditionals that depend on the object state. 

    # to do: change to php
    class Document is
        field state: string
        // ...
        method publish() is
            switch (state)
                "draft":
                    state = "moderation"
                    break
                "moderation":
                    if (currentUser.role == "admin")
                        state = "published"
                    break
                "published":
                    // Do nothing.
                    break
        // ...
  • Adapter Pattern

    Software systems sometimes face the compatibility issue, when the output of one system may not conform with the expected input of another system, in this cases we use a adapter.

    Adapter is a structural design pattern that allows objects with incompatible interfaces to collaborate.

    The adapter essentially encapsulates the adaptee and presents a new interface, or appearance, to the client class. It does this by wrapping the adaptee’s interface and exposing a new target interface that makes sense to the client.

    <?php
    // --- Target Interface --- //
    // The interface the client expects.
    interface LoggerInterface {
        public function log(string $level, string $message): void;
    }
    
    // --- Adaptee --- //
    // Existing class with an incompatible interface.
    class LegacyLogger {
        public function writeLog(string $text): void {
            echo "Legacy log: $text\n";
        }
    }
    
    // --- Adapter --- //
    // The Adapter makes the Adaptee compatible with the Target interface.
    class LegacyLoggerAdapter implements LoggerInterface {
        private LegacyLogger $legacyLogger;
    
        public function __construct(LegacyLogger $legacyLogger) {
            $this->legacyLogger = $legacyLogger;
        }
    
        public function log(string $level, string $message): void {
            // Convert the expected format into what the legacy system understands.
            $formattedMessage = strtoupper($level) . ": " . $message;
            $this->legacyLogger->writeLog($formattedMessage);
        }
    }
    
    // --- Concrete Class (for comparison) --- //
    // A modern JSON logger that already matches the interface.
    class JsonLogger implements LoggerInterface {
        public function log(string $level, string $message): void {
            echo json_encode(['level' => $level, 'message' => $message], JSON_PRETTY_PRINT) . "\n";
        }
    }
    
    // --- Client Code --- //
    function clientCode(LoggerInterface $logger): void {
        $logger->log('info', 'Adapter pattern in action!');
    }
    
    // Use the modern logger
    echo "Using JsonLogger:\n";
    clientCode(new JsonLogger());
    
    // Use the legacy logger via adapter
    echo "\nUsing LegacyLogger through Adapter:\n";
    $legacyAdapter = new LegacyLoggerAdapter(new LegacyLogger());
    clientCode($legacyAdapter);
    
    //Output
    //Using JsonLogger
    {
        "level": "info",
        "message": "Adapter pattern in action!"
    }
    
    //Using LegacyLogger through Adapter
    Legacy log: INFO: Adapter pattern in action!
    

  • Facade & Dependency Injection (DI)

    Facade is a structural design pattern that provides a simplified interface to a library, a framework, or any other complex set of classes.

    This design pattern uses a number of different design principles, to provide ease of access to complex classes, what we call subsystem. This is done by encapsulating the subsystem classes into a Facade class, and then hiding them from the client classes so that the clients do not know about the details of the subsystem.

    When use it: Use the Facade pattern when you need to have a limited but straightforward interface to a complex subsystem.

    <?php
    // --- Subsystems --- //
    
    class InventoryService {
        public function checkStock(string $productId): bool {
            echo "Checking stock for product: $productId\n";
            // Simulated logic
            return true;
        }
    
        public function reserveItem(string $productId): void {
            echo "Reserving item: $productId\n";
        }
    }
    
    class PaymentService {
        public function processPayment(float $amount, string $method): bool {
            echo "Processing payment of $$amount via $method\n";
            // Simulated logic
            return true;
        }
    }
    
    class ShippingService {
        public function ship(string $productId, string $address): void {
            echo "Shipping product '$productId' to $address\n";
        }
    }
    
    class NotificationService {
        public function sendConfirmation(string $email, string $message): void {
            echo "Sending confirmation email to $email: $message\n";
        }
    }
    
    // --- Facade --- //
    
    class OrderFacade {
        private InventoryService $inventory;
        private PaymentService $payment;
        private ShippingService $shipping;
        private NotificationService $notification;
    
        public function __construct() {
            $this->inventory = new InventoryService();
            $this->payment = new PaymentService();
            $this->shipping = new ShippingService();
            $this->notification = new NotificationService();
        }
    
        /**
         * Simplified order process using the Facade
         */
        public function placeOrder(string $productId, float $amount, string $paymentMethod, string $address, string $customerEmail): void {
            echo "Starting order process...\n";
    
            if (!$this->inventory->checkStock($productId)) {
                echo "Product not available in stock.\n";
                return;
            }
    
            $this->inventory->reserveItem($productId);
    
            if (!$this->payment->processPayment($amount, $paymentMethod)) {
                echo "Payment failed. Order canceled.\n";
                return;
            }
    
            $this->shipping->ship($productId, $address);
            $this->notification->sendConfirmation($customerEmail, "Your order for '$productId' has been successfully processed!");
    
            echo "Order completed successfully.\n";
        }
    }
    
    // --- Client Code --- //
    $orderFacade = new OrderFacade();
    $orderFacade->placeOrder(
        productId: 'SKU12345',
        amount: 199.99,
        paymentMethod: 'Credit Card',
        address: '123 Business Street, Cityville',
        customerEmail: 'customer@example.com'
    );

    As you can see, a facade class can be used to wrap all the interfaces and classes for a subsystem. It is your decision as to what you want to wrap.

    Now let’s improve this code by using Dependency Injection (DI) in it.

    Dependency Injection means giving an object its dependencies from something outside, instead of the object creating them by itself.

    <?php
    // --- Interfaces --- //
    
    interface InventoryInterface {
        public function checkStock(string $productId): bool;
        public function reserveItem(string $productId): void;
    }
    
    interface PaymentInterface {
        public function processPayment(float $amount, string $method): bool;
    }
    
    interface ShippingInterface {
        public function ship(string $productId, string $address): void;
    }
    
    interface NotificationInterface {
        public function sendConfirmation(string $email, string $message): void;
    }
    
    // --- Concrete Implementations --- //
    
    class InventoryService implements InventoryInterface {
        public function checkStock(string $productId): bool {
            echo "Checking stock for product: $productId\n";
            return true;
        }
    
        public function reserveItem(string $productId): void {
            echo "Reserving item: $productId\n";
        }
    }
    
    /**
     * Stripe payment service
     */
    class StripePaymentService implements PaymentInterface {
        public function processPayment(float $amount, string $method): bool {
            echo "Processing payment of $$amount via $method\n";
            return true;
        }
    }
    
    /**
     * PayPal payment service
     */
    class PaypalPaymentService implements PaymentInterface {
        public function processPayment(float $amount, string $method): bool {
            echo "Processing PayPal payment of $$amount using $method\n";
            return true;
        }
    }
    
    class ShippingService implements ShippingInterface {
        public function ship(string $productId, string $address): void {
            echo "Shipping product '$productId' to $address\n";
        }
    }
    
    class NotificationService implements NotificationInterface {
        public function sendConfirmation(string $email, string $message): void {
            echo "Sending confirmation email to $email: $message\n";
        }
    }
    
    // --- Facade --- //
    
    class OrderFacade {
        public function __construct(
            private InventoryInterface $inventory,
            private PaymentInterface $payment,
            private ShippingInterface $shipping,
            private NotificationInterface $notification
        ) {}
    
        /**
         * High-level method hiding all subsystem complexity
         */
        public function placeOrder(
            string $productId,
            float $amount,
            string $paymentMethod,
            string $address,
            string $customerEmail
        ): void {
            echo "Starting order process...\n";
    
            if (!$this->inventory->checkStock($productId)) {
                echo "Product not available in stock.\n";
                return;
            }
    
            $this->inventory->reserveItem($productId);
    
            if (!$this->payment->processPayment($amount, $paymentMethod)) {
                echo "Payment failed. Order canceled.\n";
                return;
            }
    
            $this->shipping->ship($productId, $address);
            $this->notification->sendConfirmation(
                $customerEmail,
                "Your order for '$productId' has been successfully processed!"
            );
    
            echo "Order completed successfully.\n";
        }
    }
    
    // --- Client Code --- //
    $inventory = new InventoryService();
    $payment = new PaypalPaymentService();
    $shipping = new ShippingService();
    $notification = new NotificationService();
    
    $orderFacade = new OrderFacade($inventory, $payment, $shipping, $notification);
    
    $orderFacade->placeOrder(
        productId: 'SKU12345',
        amount: 199.99,
        paymentMethod: 'PayPal',
        address: '123 Business Street, Cityville',
        customerEmail: 'customer@example.com'
    );
    

    What changed?

    • Interfaces: define contracts for each subsystem. You can swap implementations easily (e.g., replace PayPalPaymentService with StripePaymentService).
    • Dependency Injection: the Facade doesn’t create its dependencies — they’re injected externally in client code.
    • Loose coupling: classes depend on abstractions, not concrete classes.
    • Testable: in unit tests, you can inject mock objects.

  • Factory Method

    Factory method provides an interface for creating objects in a superclass,
    allows subclasses to decide wich class to instantiate.

    //Concrete or direct instantiation. Client code with no factory.
    $myEmailNotification = new emailNotification;
    $mySmsNotification = new smsNotification;
    
    //Abstracted object creation. Client code with a factory-based instantiation.
    $myEmailNotification = NotificationFactory::create('email');
    $mySmsNotification = NotificationFactory::create('sms');

    Factories allow client code to operate on generalizations, this is called coding to an interface, not an implementation and is a fundamental principle in object-oriented programming that promotes flexibility, maintainability, and testability in software design. 

    It means that client code should interact with a system through an abstract interface (or abstract class) rather than create the object directly, without worrying about the details of object creation. 

    <?php
    // interface defines a contract — all notification classes must have a send() method.
    interface Notification
    {
        public function send(string $to, string $message): void;
    }
    
    // Concrete implementations of different notification types
    
    class EmailNotification implements Notification
    {
        public function send(string $to, string $message): void
        {
            echo "Sending EMAIL to {$to}: {$message}" . PHP_EOL;
        }
    }
    
    class SmsNotification implements Notification
    {
        public function send(string $to, string $message): void
        {
            echo "Sending SMS to {$to}: {$message}" . PHP_EOL;
        }
    }
    
    class PushNotification implements Notification
    {
        public function send(string $to, string $message): void
        {
            echo "Sending PUSH notification to {$to}: {$message}" . PHP_EOL;
        }
    }
    
    // The Factory class — decides which notification to create
    class NotificationFactory
    {
        public static function create(string $type): Notification
        {
            return match (strtolower($type)) {
                'email' => new EmailNotification(),
                'sms'   => new SmsNotification(),
                'push'  => new PushNotification(),
                default => throw new InvalidArgumentException("Unknown notification type: {$type}")
            };
        }
    }
    
    // Usage exemple
    try {
        $notification = NotificationFactory::create('email');
        $notification->send('user@example.com', 'Your order has been shipped.');
    
        $notification = NotificationFactory::create('sms');
        $notification->send('+5547999999999', 'Your code is 123456.');
    
        $notification = NotificationFactory::create('push');
        $notification->send('User123', 'You have a new message.');
    } catch (Exception $e) {
        echo "Error: " . $e->getMessage();
    }

  • Singleton

    Singleton – ensure that a class has only one instance,
    while providing a global access to this instance.

    <?php
    
    class Logger
    {
        private static ?Logger $uniqueInstance = null;
        private array $logs = [];
    
        // prevents direct instantiation
        private function __construct() {}
    
        // no one outside the class can duplicate the instance.
        private function __clone() {}
    
        /* __wakeup must be public (by PHP’s design), 
        since it’s automatically called during unserialize().*/
        public function __wakeup()
        {
            throw new \Exception("Cannot unserialize a singleton.");
        }
    
        /* Lazy construction / initialization - returns the single instance 
        of this class*/
        public static function getInstance(): Logger
        {
            if (self::$uniqueInstance === null) {
                self::$uniqueInstance = new Logger();
            }
            return self::$uniqueInstance;
        }
    
        // Methods
        public function addLog(string $message): void
        {
            $this->logs[] = $message;
        }
    
        public function showLogs(): void
        {
            foreach ($this->logs as $log) {
                echo $log . PHP_EOL;
            }
        }
    }
    
    // Usage exemple
    $logger1 = Logger::getInstance();
    $logger2 = Logger::getInstance();
    
    $logger1->addLog("First log entry.");
    $logger2->addLog("Second log entry.");
    
    $logger1->showLogs();
    
    //Checking the solid proof...
    
    if ($logger1 === $logger2) {
        echo "Same instance" . PHP_EOL;
    } else {
        echo "Different instances" . PHP_EOL;
    }
    
    echo "Logger1 ID: " . spl_object_id($logger1) . PHP_EOL;
    echo "Logger2 ID: " . spl_object_id($logger2) . PHP_EOL;
    
    /*
    Output:
    
    First log entry.
    Second log entry.
    Same instance
    Logger1 ID: 1
    Logger2 ID: 1
    */

    The code above works perfectly for our purpose, but it’s not inheritance-safe, meaning it always creates a Logger instance, even if called from a subclass. Therefore:

    class FileLogger extends Logger {}
    FileLogger::getInstance(); //still returns the instance of Logger, not a instance of FileLogger

    If we want that each subclass has its own singleton, we must change this behavior by modifying the getInstance() method, like this:

    public static function getInstance(): Logger 
    {
        $className = static::class;
        if (!isset(self::$uniqueInstance[$className])) {
            self::$uniqueInstance[$className] = new static();
        }
        return self::$uniqueInstance[$className];
    }

    Comparison:

    FeatureVersion
    self
    Version
    static::class
    Supports inheritance❌ No✅ Yes
    Each subclass has its own singleton❌ No✅ Yes
    Refers to who?Refer to the class where the code was originally defined.Refer to the class that was actually called at runtime.
    When use?You want a single, strict singleton for only the original class.You expect inheritance or extensions of your original class.

    When Singleton pattern is useful?

    • Logger – Keep a single instance that writes all logs from different parts of the app.
    • Configuration Manager – Store settings (like API keys, database credentials, etc.) in one place.
    • Database Connection – Maintain one connection to the database.
    • Cache Manager – Keep a single cache handler (like Redis, Memcached, or file cache)
    • Session Manager – Handle user session data globally.
    • Event Dispatcher or Queue Manager – Centralize how events or background tasks are managed.

  • Classes: comparison of the “is-a” vs “has-a” relationship

    Composition (“has-a”) or Inheritance (“is-a” ), how to choose the corret relationship?

    First of all, you have to know that, in the scenario of modern object-oriented design principles, there is a strong preference for Composition over Inheritance as better design principle.

    This happens because, once you extend a class (inherit from it), you become bound to its structure and behavior, making it difficult to change it later. Let’s see a brief about each relantionship below.

    “Is-a” → Inheritance
    Used when one class is a type of another class.

    Example:
    Car is-a Vehicleclass Car extends Vehicle


    “Has-a” → Composition / Aggregation
    Used when one class contains or uses another class.

    Example:
    Car has-a Engineclass Car { private Engine $engine; }


    How to choose?

    • Use is-a when you want to reuse behavior and the relationship is naturally hierarchical.
    • Use has-a when you want flexibility, replaceable components, and loose coupling.

    Code exemples

    IS-A (Inheritance) – Use when a class is a type of another class.

    <?php
    
    class Vehicle
    {
        public function move()
        {
            echo "The vehicle is moving\n";
        }
    }
    
    class Car extends Vehicle   // Car *is-a* Vehicle
    {
    }
    
    $car = new Car();
    $car->move(); // inherited

    HAS-A (Composition) – Use when a class contains another class.

    <?php
    
    class Engine
    {
        public function start()
        {
            echo "Engine started\n";
        }
    }
    
    class Car
    {
        private Engine $engine; // Car *has-a* Engine
    
        public function __construct()
        {
            $this->engine = new Engine();
        }
    
        public function turnOn()
        {
            $this->engine->start();
        }
    }
    
    $car = new Car();
    $car->turnOn();

    Now, let’s improve the HAS-A, with a more flexible and modern example using interfaces + dependency injection, allowing a car to use diesel, gasoline, ethanol (alcohol), electric, or any other engine.

    //Interface
    <?php
    
    interface EngineInterface
    {
        public function start(): void;
    }
    
    //Different engines
    class DieselEngine implements EngineInterface
    {
        public function start(): void
        {
            echo "Diesel engine roaring...\n";
        }
    }
    
    class GasEngine implements EngineInterface
    {
        public function start(): void
        {
            echo "Gas engine starting...\n";
        }
    }
    
    class AlcoholEngine implements EngineInterface
    {
        public function start(): void
        {
            echo "Alcohol engine warming up...\n";
        }
    }
    
    //Car uses composition + dependency injection
    class Car
    {
        private EngineInterface $engine; // Car *has-a* Engine
    
        public function __construct(EngineInterface $engine)
        {
            $this->engine = $engine;
        }
    
        public function turnOn(): void
        {
            $this->engine->start();
        }
    }
    
    //Using it...
    $RangeRover = new Car(new DieselEngine());
    $RangeRover->turnOn();
    
    $Civic = new Car(new GasEngine());
    $Civic->turnOn();
    
    $Fiat147 = new Car(new AlcoholEngine());
    $Fiat147->turnOn();
    
    /*
    Output:
    
    Diesel engine roaring...
    Gas engine starting...
    Alcohol engine warming up...
    */
  • Observer Pattern and the old good MVC

    Observer Pattern and the old good MVC

    Trygve Reenskaug developed the MVC pattern during his time as a visiting scientist at Xerox Palo Alto Research Center (PARC) while working on Smalltalk-79 in the late 1970s. He aimed to create a structure suitable for programs that allowed users to interact with complex and extensive data sets.

    Trygve Reenskaug

    His first version of the design included four components: model, view, thing, and editor. After collaborating and discussing the idea with other Smalltalk developers, the team refined the concept into the now-familiar three parts: model, view, and controller (MVC).

    The MVC pattern separates the responsibilities of a system into three distinct components. To understand this better, consider a simple diagram of the MVC structure.

    Model

    Starting with the model — it holds the core data and the business logic that users interact with and modify. For example, in a grocery store system, both cashiers and customers use the model to create and manage orders. An important aspect of MVC is that the model operates independently and does not depend on the other parts of the system, there fore, it contains all the state, methods, and data required for it to function independently.

    View

    As the name suggests, it provides a way for users to visualize the model or specific parts of it. In a grocery store system, for instance, the view might be the display showing the list of items and their prices.

    Often, the view also includes interactive components such as buttons and input fields that let users engage with the system.

    Essentially, the model represents the backend — the underlying logic and data that power the application, and the view the front end, the presentation layer. It is possible to have several views, all used within the same model.

    Observer Design Pattern

    When some value changes in the backend or the model, it should ideally tell the view to update itself accordingly. This is done by using the Observer Design Pattern. In the observer pattern, the observers act like subscribers. In this case, any view is also an observer. When the model changes, it notifies all the views that are subscribed to it.