Fellipe Sanches' website

Tag: pattern

  • 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.

  • Liskov Substitution Principle

    First of all, Liskov was a woman, okay? That being said, let’s continue; In object-oriented programming, classes are user-defined data structures that group related attributes and methods, allowing developers to model real-world objects or abstract ideas.

    These classes can relate to one another through inheritance, a key concept that lets subclasses acquire its properties and behaviors.

    Inheritance promotes code reuse and specialization, but it must be used carefully. The Liskov Substitution Principle (LSP) provides guidance: if a subclass is a subtype of a base class, it should be able to replace it without altering the program’s behavior.

    To satisfy the LSP, subclasses must not change the meaning of methods, weaken postconditions, or modify immutable attributes from the base class. They can, however, extend functionality or improve performance, as long as the expected results remain consistent.

    In short, inheritance should maintain consistency between base and derived classes, ensuring that substituting one for the other keeps the system stable and predictable.

  • Open/Closed Principle

    All design patterns follow a basic set of design principles, which address issues such as flexibility and reusability.

    One of these principles is called the Open/Closed Principle.

    The Open/Closed Principle is a concept that helps keep a system stable by closing classes to changes and allowing the system to open for extension through the use of inheritance or interfaces.

    You should consider a class as being “closed” to changes once it has been tested to be functioning properly.

    The class should be behaving as you would expect it to behave. All the attributes and behaviors are encapsulated and proven to be stable within your system.

    The class, or any instance of the class, should not stop your system from running or do any harm to it.

    The closed portion of the principle doesn’t mean that you can’t go back to a class to make changes to it during development. So, what do you do if you need to add more features to extend your systems? There are two different ways to do it.

    The first way is through inheritance of a superclass. The idea is that when you want to add more attributes and behaviors to a class that is considered closed, you can simply use inheritance to extend it. Now let’s an exemple that will clarify this concept:

    <?php
    // --- Base class (tested and considered stable) --- //
    class PaymentProcessor {
        public function process(float $amount): void {
            echo "Processing a payment of $$amount...\n";
        }
    }
    
    // --- Open for extension: Inheritance --- //
    // Instead of changing the original PaymentProcessor class,
    // we extend it to add new behavior (e.g., logging).
    class LoggedPaymentProcessor extends PaymentProcessor {
        public function process(float $amount): void {
            echo "[LOG] About to process payment.\n";
            parent::process($amount);
            echo "[LOG] Payment successfully processed.\n";
        }
    }
    
    // --- Closed for modification: Final class --- //
    // Once a class is stable and we don’t want further extension,
    // we can mark it as 'final' to prevent inheritance.
    final class SecurePaymentProcessor extends PaymentProcessor {
        public function process(float $amount): void {
            echo "Processing a secure payment of $$amount...\n";
        }
    }
    
    // --- Usage examples --- //
    echo "== Open for extension ==\n";
    $loggedProcessor = new LoggedPaymentProcessor();
    $loggedProcessor->process(100);
    
    echo "\n== Closed for extension ==\n";
    $secureProcessor = new SecurePaymentProcessor();
    $secureProcessor->process(200);

    This way helps preserve the integrity of the superclass but lets you still have extra features via subclasses.

    You also may reach a point where you no longer want a class to be extendable, in which case you can declare a class to be final, which will prevent further inheritance.

    The second way, a class can be considered open to extension, is if the class is abstract and enforces the Open/Closed Principle through polymorphism. Let see it below:

    <?php
    // --- Abstract class defining the base structure --- //
    abstract class PaymentProcessor {
        // Abstract method: subclasses must define this
        abstract public function process(float $amount): void;
    
        // Concrete method: shared logic
        protected function log(string $message): void {
            echo "[LOG] $message\n";
        }
    }
    
    // --- Concrete subclass 1 --- //
    class CreditCardProcessor extends PaymentProcessor {
        public function process(float $amount): void {
            $this->log("Processing credit card payment of $$amount...");
            echo "Credit card payment completed.\n";
        }
    }
    
    // --- Concrete subclass 2 --- //
    class PaypalProcessor extends PaymentProcessor {
        public function process(float $amount): void {
            $this->log("Processing PayPal payment of $$amount...");
            echo "PayPal payment completed.\n";
        }
    }
    
    // --- Usage --- //
    $processors = [
        new CreditCardProcessor(),
        new PaypalProcessor()
    ];
    
    foreach ($processors as $processor) {
        $processor->process(150);
    }

    An abstract class can declare abstract methods with just the method signatures. Each concrete subclass must provide their own implementation of these methods. The methods in the abstract superclass are preserved, and you can extend your system by providing different implementations for each method.

    This can be useful for behaviors that can be accomplished in different ways, like sorting and searching. You can also use an interface to enable polymorphism, but in this case you won’t be able to define a common set of attributes.

    The Open/Closed Principle is used to keep the stable parts of your system separate from the varying parts, it’s a principle you should use when designing and building your software solutions.

    Now you know how to add more features to your system without disrupting something that works. Use it!