Fellipe Sanches' website

Tag: solid

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