Principio de Sustitución de Liskov en diseño orientado a objetos (SOLID)

Author
By Darío Rivera
Posted on 2021-11-19 in SOLID

En un anterior post vimos una introducción a los Principios SOLID a manera meramente teórica. El día de hoy veremos todo acerca del tercer principio SOLID en la lista, The Liskov Substitution Principle. Empecemos por dar una definición de este principio.

Definición

Este principio dice que los objetos de un programa pueden ser reemplazados por sus subtipos sin alterar el correcto funcionamiento del programa. Es decir, un argumento de una función o método que acepte una clase o interfaz, debería funcionar igual con cualquier subtipo de la misma clase o interfaz. Para que esto sea posible, es necesario que la signature de los métodos implementados o sobreescritos sean iguales y no agreguen o cambien el TIPO del resultado de la función (incluyendo excepciones).

Ejemplo

Para ilustrar este principio, vamos a ver un ejemplo práctico de una correcta implementación y algunos errores comunes que pueden romper dicho principio. Supongamos que tenemos una clase Result que encapsula una cadena resultante de datos.

class Result
{
    private string $content;    

    public function __construct(string $content)
    {
        $this->content = $content;
    }

    public function getContent(): string
    {
        return $this->content;
    }
}

Esta cadena es resultado de cómo se parsea un array multidimensional que simula una tabla de datos como la siguiente:

[
    [1, 'Steave', 'Developer'],
    [2, 'Andreas', 'Tester'],
]

Cada subtipo de la clase Parser será una forma de generar la cadena de contenido, por ejemplo, CSV o una tabla en markdown.

class Parser
{
    protected array $data;

    public function __construct(array $data)
    {
        $this->data = $data;
    }

    public function output()
    {
        $result = '';

        foreach($this->data as $row)
        {
            $result .= $this->line($row);
        }

        return new Result($result);
    }

    public function line(array $row)
    {
        return implode(' ', $row) . PHP_EOL;
    }
}
class CSV extends Parser
{
    public function line(array $row)
    {
        return implode(';', $row) . PHP_EOL;
    }
}
class MarkdownTable extends Result
{
    public function line(array $row)
    {
        return '|' . implode('|', $row) . '|' . PHP_EOL;
    }
}

De este modo el código "cliente" podría hacer uso del tipo Parser del la siguiente forma.

class Client
{
    public static function printContent(Parser $parser)
    {
        echo $parser->output();
    }
}

Esto significa que para cumplir con el principio de sustitución de Liskov, el método printContent debería poder aceptar cualquier subtipo de Parser y el código seguiría funcionando igual.

$parser = new Parser([
    [1, 'Steave', 'Developer'],
    [2, 'Andreas', 'Tester'],
]);

// subtype CSV
$csv = new CSV([
    [1, 'Steave', 'Developer'],
    [2, 'Andreas', 'Tester'],
]);

// subtype MarkdownTable
$marrkdown = new MarkdownTable([
    [1, 'Steave', 'Developer'],
    [2, 'Andreas', 'Tester'],
]);

Client::printContent($parser);
Client::printContent($csv);
Client::printContent($markdown);

Errores comunes

Un error común suele presentarse al agregar precondiciones a las implementaciones. Por ejemplo, podemos verificar si al menos existen dos filas para determinar el encabezado de la tabla de markdown

class MarkdownTable extends Parser
{
    public function output()
    {
        if (count($this->data) < 2) {
            throw new \Exception('Missing table header');
        }

        return parent::output();
    }

    public function line(array $row)
    {
        return '|' . implode('|', $row) . '|' . PHP_EOL;
    }
}

Entregar otro tipo de datos diferente en un subtipo también rompe el principio de sustitución de Liskov

class MarkdownTable extends Parser
{
    public function output()
    {
        return MarkdownWrapper(parent::output());
    }
}

Acerca de Darío Rivera

Author

Ingeniero de desarrollo en PlacetoPay , Medellín. Darío ha trabajado por más de 6 años en lenguajes de programación web especialmente en PHP. Creador del microframework DronePHP basado en Zend y Laravel.

Sólo aquellos que han alcanzado el éxito saben que siempre estuvo a un paso del momento en que pensaron renunciar.