SOLID: Substituição de Liskov (Parte 2)

·2 min de leitura

O que é Liskov, afinal?

O Liskov Substitution Principle (LSP) costuma ser o mais “teórico” do SOLID, mas na prática ele é bem visceral:

Objetos de um supertipo devem poder ser substituídos por objetos de subtipos sem quebrar o programa.

Nome histórico: Barbara Liskov (1987). Em OO, isso é principalmente sobre herança e contratos.

O problema mental: herança “só para reusar código”

Herança promete reuso, mas entrega acoplamento. O problema aparece quando a subclasse enfraquece pré-condições, reforça pós-condições demais, lança exceções que a base não sugere ou ignora invariantes. Nesses casos, você quebrou substituibilidade: quem programou contra a base toma surpresa.

Exemplo clássico que viola LSP (quadrado/retângulo)

Em muitos materiais didáticos, modelar Square extends Rectangle vira armadilha: um quadrado não pode ter setWidth independente de height sem quebrar invariantes de retângulo. Na prática de domínio, muitas vezes a solução é não forçar essa herança, usar composição ou modelar formas de outro jeito.

Exemplo na prática: violação e correção

Violação: “implementa a interface, mas não cumpre”

<?php

declare(strict_types=1);

interface CloudStorage
{
    /** @return non-empty-string */
    public function put(string $path, string $contents): string;

    public function get(string $id): string;
}

final class LocalFakeCloud implements CloudStorage
{
    public function put(string $path, string $contents): string
    {
        throw new RuntimeException('Somente leitura neste ambiente.');
    }

    public function get(string $id): string
    {
        return '...';
    }
}

Explicação: quem depende de CloudStorage espera que put funcione quando chamado. Essa implementação quebra expectativas, é um anti-LSP comum quando “implementamos interface” só para encaixar no DI.

Correção: contratos honestos

<?php

declare(strict_types=1);

interface ReadableStorage
{
    public function get(string $id): string;
}

interface WritableStorage
{
    /** @return non-empty-string */
    public function put(string $path, string $contents): string;
}

interface CloudStorage extends ReadableStorage, WritableStorage {}

Explicação: se o ambiente só lê, injete ReadableStorage. Assim, substituir implementações não exige “métodos que explodem”.

LSP e tipagem estática

Com interface, abstract e testes, você documenta o contrato. Tipagem mais rígida na linguagem ajuda, mas semântica ainda é sua responsabilidade.

Conclusão

LSP é “não minta com herança/interface”. Se um subtipo não pode fazer o que o supertipo promete, o desenho está errado, não é o chamador que precisa “se virar”.