Fellipe Sanches' website

Category: Design Pattern

  • Interface Segregation Principle

    Interfaces let you write code that depends on abstractions, not on concrete classes.
    When you programming this way, your code becomes more flexible, easier to test, easier to replace, and less dependent on specific implementations.

    Let’s see it in some exemples below:

    //Bad :-( 
    //Programming to a concrete class with tightly coupled code
    class MySqlDatabase {
        public function connect() {
            echo "Connected to MySQL";
        }
    }
    
    class UserService {
        private MySqlDatabase $db;
    
        public function __construct() {
            $this->db = new MySqlDatabase(); // tightly coupled
        }
    
        public function loadUser() {
            $this->db->connect();
        }
    }
    
    //Good :-)
    //Define an interface and have decoupled code, like Lego bricks!
    interface DatabaseConnection {
        public function connect();
    }
    
    //Create multiple implementations
    class MySqlDatabase implements DatabaseConnection {
        public function connect() {
            echo "Connected to MySQL";
        }
    }
    
    class PostgresDatabase implements DatabaseConnection {
        public function connect() {
            echo "Connected to PostgreSQL";
        }
    }
    
    //Use only the interface inside your main class
    class UserService {
        private DatabaseConnection $db;
    
        // Any database that implements the interface can be injected
        public function __construct(DatabaseConnection $db) {
            $this->db = $db;
        }
    
        public function loadUser() {
            $this->db->connect();
        }
    }
    
    //Choose the implementation at runtime
    $service = new UserService(new PostgresDatabase()); //Postgres: I Choose You!
    $service->loadUser();
    

    Sometimes it isn’t always clear how to properly segregate your interface or to predict future changes;

    So, if in the future your interface becomes too bloated, don’t hesitate to divide it into smaller and more focused sections, each representing a single responsibility.

    When designing interfaces, you should always strive to be as precise as possible; since they describe what the parts of your system are capable of doing, the clearer that description is, the easier it will be to build, update, and maintain your software.

  • Composing Objects Principle

    When a subclass inherits from a super class, the subclass will gain knowledge and access to all of the super classes attributes and methods as long as their access modifiers are not private. 

    This means that if you have multiple levels of inheritance, a subclass at the bottom of the inheritance tree can potentially have a way to access attributes and behaviors of all the super classes. 

    It’s “bad” not because inheritance is wrong, but because deep or excessive inheritance creates several structural problems in software design.

    So, what can we do to avoid this? 

    Composing Objects Principle

    You can use the composing objects principle to gain a high amount of code reuse without using inheritance. This principle states that classes should achieve code reuse through aggregation rather than inheritance. 

    Design patterns, such as the composite design pattern and decorator design pattern use this design principle. 

    Composing objects does not force you to try and find commonalities between two classes, and couple them together like with inheritance. Instead, you’re able to design classes that can work together without having to share anything between them. This flexibility will also help you if the system requirements change.

    To clarify, let’s look at a code snippet below, comparing both modes.

    <?php
    //Inheritance way
    class Logger {
        public function log(string $message): void {
            echo "[LOG] " . $message . "<br>";
        }
    }
    
    class UserService extends Logger {
        public function createUser(string $name): void {
            $this->log("Creating user: {$name}");
            echo "User {$name} created.<br>";
        }
    }
    
    <?php
    //Composite way
    class Logger {
        public function log(string $message): void {
            echo "[LOG] " . $message . "<br>";
        }
    }
    
    class UserService {
        private Logger $logger;
    
        public function __construct(Logger $logger) {
            $this->logger = $logger;
        }
    
        public function createUser(string $name): void {
            $this->logger->log("Creating user: {$name}");
            echo "User {$name} created.<br>";
        }
    }
    

    Dynamic Behavior

    In addition to less coupling, composition does allow dynamic behavior at runtime, because you can inject different objects into a class:

    $userService = new UserService(new FileLogger());
    // later…
    $userService->setLogger(new DatabaseLogger());
    

    What you can’t do with inheritance, once this relationship is fixed in the source code:

    class UserService extends Logger {}

    Now, it’s not to say that inheritance should never be used. Composing objects in inheritance both have their place. Composing will give you better flexibility and less coupling while maintaining reusability, but that doesn’t mean it should always be used over inheritance. 

    You need to examine the needs of your system in order to determine which design principle is appropriate. 

    • Do you have a set of related classes or unrelated classes? 
    • What is a common behavior between them? 
    • Do you need specialized classes to handle specific cases or do you simply need a different implementation of the same behavior? 

    This are the different kinds of questions that you’ll need to ask yourself when designing your software systems.

  • 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 Vehicle → class Car extends Vehicle


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

    Example:
    Car has-a Engine → class 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...
    */