El principio de sustitución de Barbara Liskov (condiciones previas y posteriores)

¿Por qué tantos tienen problemas con este principio? Si no tomamos "abstruso", sino una definición más simple, entonces suena así:





La clase heredada debe complementar, no anular, el comportamiento de la clase base.





Suena claro y bastante lógico, no estamos de acuerdo. pero maldita sea, ¿cómo lograr esto? Por alguna razón, muchas personas simplemente omiten información sobre condiciones previas y posteriores , que explican perfectamente lo que se debe hacer.





En este artículo, NO consideraremos ejemplos generales de este principio, sobre el cual ya existen muchos materiales (ejemplo con un cuadrado y un rectángulo o controles de termostato ). Aquí nos detendremos con un poco más de detalle en conceptos tales como "Precondiciones" , "Poscondiciones" , consideraremos qué son la covarianza, contravarianza e invariancia , así como qué son las "limitaciones históricas" o la "regla de la historia".





Las condiciones previas no se pueden reforzar en una subclase

️En otras palabras, las clases secundarias no deben crear más condiciones previas que las definidas en la clase base para realizar algún comportamiento comercial. He aquí un ejemplo:





<?php

class Customer
{
    protected float $account = 0;

    public function putMoneyIntoAccount(int|float $sum): void
    {
        if ($sum < 1) {
            throw new Exception('       1$');
        }

        $this->account += $sum;
    }
}

class  MicroCustomer extends Customer
{
    public function putMoneyIntoAccount(int|float $sum): void
    {
        if ($sum < 1) {
            throw new Exception('       1$');
        }

        //  
        if ($sum > 100) { 
            throw new Exception('      100$');
        }

        $this->account += $sum;
    }
}
      
      



​ . !





«», , .





, , .





, , Bar->process()



, .





<?php

class Foo
{
    public function process(int|float $value)
    {
       // some code
    }
}

class Bar extends Foo
{
    public function process(int|float|string $value)
    {
        // some code
    }
}
      
      



, VIPCustomer



putMoneyIntoAccount



( ) Money



, ( Dollars



).





<?php

class Money {}
class Dollars extends Money {}

class Customer
{
    protected Money $account;

    public function putMoneyIntoAccount(Dollars $sum): void
    {
        $this->account = $sum;
    }
}

class VIPCustomer extends Customer
{
    public function putMoneyIntoAccount(Money $sum): void
    {
        $this->account = $sum;
    }
}
      
      



, , .





​️ , . .





<?php

class Customer
{
    protected Dollars $account;

    public function chargeMoney(Dollars $sum): float
    {
        $result = $this->account - $sum->getAmount();

        if ($result < 0) { // 
            throw new Exception();
        }

        return $result;
    }
}

class  VIPCustomer extends Customer
{
    public function chargeMoney(Dollars $sum): float
    {
        $result = $this->account - $sum->getAmount();

        if ($sum < 1000) { //   
            $result -= 5;  
        }
       
        //    
      
        return $result;
    }
}
      
      



​ , . !





- «», (?!), .





. render()



, JpgImage



, Image



, Renderer



.





<?php

class Image {}
class JpgImage extends Image {}

class Renderer
{
    public function render(): Image
    {
    }
}

class PhotoRenderer extends Renderer
{
    public function render(): JpgImage
    {
    }
}
      
      



​️ . . :)





.





- .





— , . , .





.





<?php 

class Wallet
{
    protected float $amount;
    //        
}
      
      



(« »):





.





, . , . 





<?php

class Deposit
{
    protected float $account = 0;

    public function __construct(float $sum)
    {
        if ($sum < 0) {
            throw new Exception('      ');
        }

        $this->account += $sum;
    }
}

class VipDeposit extends Deposit
{
    public function getMoney(float $sum)
    {
        $this->account -= $sum;
    }
}
      
      



Deposit



. VipDeposit



, account



, Deposit



. .





.





, , , , .





Vale la pena mencionar que debe esforzarse por deshacerse de las condiciones previas y posteriores. Idealmente, deberían definirse como parámetros de entrada / salida del método (por ejemplo, pasando objetos de valor listos para usar a la firma y devolviendo un objeto válido específico a la salida).





Espero que haya sido útil.





Fuentes de

  1. Wiki - Principio de sustitución de Barbara Liskov





  2. Metanit





  3. PHP.watch





  4. Canal de Telegram, con notas breves








All Articles