SOLID: Substituição de Liskov (Parte 2)
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”.