SOLID e o princípio do 6 por meia dúzia

Fala devs, cá estou eu falando sobre SOLID novamente. Para quem não vem acompanhando, já publiquei dois artigos sobre SOLID, o primeiro foi sobre o SOLID e o princípio do não faça mais do que não deve e o segundo foi sobre o SOLID e o princípio do faça algo, mas do seu jeito. Aconselho fortemente a lerem.

Bem, iniciando realmente sobre o assunto deste artigo, falarei hoje sobre o Liskov Substitution Principle (LSP), em português: Princípio da Substituição de Liskov.

Problema

E como todos já sabem, vamos ver uma história mostrando o problema que esse princípio busca resolver.

Digamos que abrimos uma lanchonete. Passado algum tempo, essa lanchonete deu muito lucro e resolvemos abrir algumas filiais. Todas as filiais seguem o cardápio base da matriz, porém, uma filial situada em determinada região, não serve o hambúrguer PHP. Então, em teoria, éramos para frequentar qualquer filial que os pratos seriam os mesmo, porém, uma dessas filias fere essa teoria.

Assim, temos um problema na qual temos filiais da nossa rede, que não seguem o cardápio. E não há garantia que outras filias sigam corretamente o nosso cardápio.

Agora que vimos a história acima, vamos para um problema de código. Iremos supor, que precisamos consumir APIs para acompanhar a entrega de produtos. Precisamos de dois tipos de pesquisa: pesquisa por um código fechado, que trás algumas informações extras, e pesquisa por código aberto onde usuários poderão acompanhar a entrega.

Nosso cliente pediu para que testassemos diversos serviços para ver qual se encaixa melhor, além disso, podemos ter um melhor desempenho, economia, etc.

<?php

class FollowPostSearcher
{
    // Alguma lógica interna

    public function findByInternalCode(string $code)
    {
        // Lógica de busca por código privado com FollowPost API
    }

    public function findByExternalCode(string $code)
    {
        // Lógica de busca por código público com FollowPost API
    }
}

class TransportXPTOSearcher extends PostSearcher
{
    public function findByExternalCode(string $code)
    {
        // Lógica de busca por código público com TransportXPTO API
    }
}

class FlashDeliverySearcher extends PostSearcher
{
    public function findByInternalCode(string $code)
    {
        // Lógica de busca por código privado com FlashDelivery API
    }

    public function findByExternalCode(string $code)
    {
        // Lógica de busca por código público com  FlashDelivery API
    }
}

class Search
{
    private FollowPostSearcher $followPostSearcher;

    public function __construct(FollowPostSearcher $followPostSearcher)
    {
        $this->followPostSearcher = $followPostSearcher;
    }

    public function searchInternal(string $code)
    {
        return $this->followPostSearcher->findByInternalCode($code);
    }

    public function searchExternal(string $code)
    {
        return $this->followPostSearcher->findByExternalCode($code);
    }
}

Dado o código acima, podemos ver que temos uma classe base de busca, que está se comunicando com determinda API para fazer a pesquisa, e algumas subclasses, que estão sobrescrevendo o comportamento interno e consumindo outras APIs. O grande problema, é que em nossa subclasse TransportXPTOPostSearcher, não implementamos o método de pesquisa por código privado. Sendo assim, podemos ter erros na hora de usar recursos dessas subclasses.

O caso é que uma de nossas subclasses não entrega o comportamento esperado. Nós temos uma subclasse que implementa por completo os recursos e outra que implementa parcialmente.

Princípio da Substituição de Liskov

Podemos definir esse princípio, como:

Um subtipo pode ser substituído por outro subtipo.

Em outras palavras, nós temos uma classe base, onde criamos subtipos dessa classe, e nós podemos substituir as chamas de um subtipo por outro. No exemplo acima, eu utilizei subtipos por meios de Herança, mas poderíamos ter o mesmo resultado, caso usássemos Interface.

Então, com base no conhecimento acima, poderíamos modificar o código e deixar da seguinte forma:

<?php

interface InternalCodeSearcher
{
    public function findByInternalCode(string $code);
}

interface ExternalCodeSearcher
{
    public function findByExternalCode(string $code);
}

class FollowPostSearcher implements InternalCodeSearcher, ExternalCodeSearcher
{
    // Alguma lógica interna

    public function findByInternalCode(string $code)
    {
        // Lógica de busca por código privado com FollowPost API
    }

    public function findByExternalCode(string $code)
    {
        // Lógica de busca por código público com FollowPost API
    }
}

class TransportXPTOSearcher implements InternalCodeSearcher
{
    public function findByInternalCode(string $code)
    {
        // Lógica de busca por código privado com TransportXPTO API
    }
}

class FleshDeliverySearcher implements InternalCodeSearcher, ExternalCodeSearcher
{
    public function findByInternalCode(string $code)
    {
        // Lógica de busca por código privado com FlashDelivery API
    }

    public function findByExternalCode(string $code)
    {
        // Lógica de busca por código público com  FlashDelivery API
    }
}

class Search
{    
    public function searchInternal(InternalCodeSearcher $internalCodeSearcher, string $code)
    {
        return $internalCodeSearcher->findByInternalCode($code);
    }

    public function searchExternal(ExternalCodeSearcher $externalCodeSearcher, string $code)
    {
        return $externalCodeSearcher->findByExternalCode($code);
    }
}

Nós quebramos o nosso código, criando partes mais específicas, garantindo que uma classe contém determinada ação. Podemos substituir uma classe de determinado tipo, por outra do mesmo tipo. Na chamada da ação searchExternal, podemos passar qualquer classe que seja do tipo do ExternalCodeSearcher, da mesma forma a pesquisa interna, nós especificamos cada vez mais, permitindo uma troca sem efeitos colaterais em nossa implementação.

Resumo

Pessoal, vou ser bem sincero com vocês, esse para mim é o princípio mais complexo/chato de ser entendido. Eu estava aqui pensando em uma forma para não violarem esse princípio, sigam essa receita: primeiro apliquem o SRP, segundo, lembrem-se do OCP e em terceiro, se minha subclasse não pode ser trocada por outra subclasse do mesmo tipo, significa que ela não faz parte daquele tipo.

Foi muito difícil pensar em exemplos e finalizando este artigo, não fiquei totalmente feliz com o conteúdo criado. Espero do fundo do meu coração que ele seja útil, que eu tenha conseguido explicar e mostrar os benefícios de LSP de uma forma clara.

10