DEV Community

Lucas Cavalcante
Lucas Cavalcante

Posted on

Princípios SOLID: o que são e como aplicá-los no PHP/Laravel (Parte 03 - Substituição de Liskov)

Dando continuidade à série de artigos sobre princípios SOLID, hoje vou falar sobre o terceiro dos princípios.

Liskov Substitution Principle

(Princípio da Substituição de Liskov)

Uma classe derivada deve ser substituída por sua classe base.

Este princípio tem este nome porque foi introduzido por Barbara Liskov em sua apresentação "Data Abstraction" em 1987. Alguns anos mais tarde, ela publicou um artigo, junto com Jeanette Wing, onde elas definiram o princípio como:

Seja Φ(x) uma provável propriedade sobre objetos x do tipo T. Então Φ(y) deve ser verdadeira para objetos y do tipo S, em que S é um subtipo de T.

Uma definição um tanto quanto complexa, não é mesmo? Normal, quando se trata de um artigo científico. Vamos simplificar e ver como isso se aplica na prática.

Continuando o nosso exemplo de Employees e Contractors, no primeiro artigo dessa série eu criei 2 repositories para ambos os perfis de usuário, e os injetei como dependência no UserService.

class UserService
{
    protected $employeeRepository;
    protected $contractorRepository;

    public function __construct(
        EmployeeRepository $employeeRepository,
        ContractorRepository $contractorRepository
    )
    {
        $this->userRepository = $userRepository;
        $this->contractorRepository = $contractorRepository;
    }

    public function register(Request $request)
    {
        $attributes = $request->all();
        if($attributes['type'] === 'employee') {
            $this->employeeRepository->save($attributes);
        }

        if($attributes['type'] === 'contractor') {
            $this->contractorRepository->save($attributes);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

No segundo artigo da série, eu criei um arquivo BaseRepository que seria a classe pai de EmployeeRepository e ContractorRepository.

É muito simples aplicar a substituição de Liskov.

Primeiro vamos criar um repository intermediário entre a classe pai e as classes filhas.

class BaseRepository
{
    // seu código
}

class UserRepository extends BaseRepository
{
    // seu código
}

class EmployeeRepository extends UserRepository
{
    // seu código
}

class ContractorRepository extends UserRepository
{
    // seu código
}
Enter fullscreen mode Exit fullscreen mode

Dessa forma, a BaseRepository continua sendo "base" para outros repositories também, e a UserRepository vira a classe pai das outras duas.

Agora vamos fazer uma pequena alteração no classe UserService.

class UserService
{
    protected $employeeRepository;
    protected $contractorRepository;

    public function __construct(
        EmployeeRepository $employeeRepository,
        ContractorRepository $contractorRepository
    )
    {
        $this->userRepository = $userRepository;
        $this->contractorRepository = $contractorRepository;
    }

    public function checkUserType(Request $request)
    {
        $attributes = $request->all();
        if($attributes['type'] === 'employee') {
            $this->register($attributes, $this->employeeRepository);
        }

        if($attributes['type'] === 'contractor') {
            $this->register($attributes, $this->contractorRepository);
        }
    }

    public function register(array $attributes, UserRepository $userRepository)
    {
        $userRepository->save($attributes);
    }
}
Enter fullscreen mode Exit fullscreen mode

Renomeei o método register() para checkUserType() onde continua sendo feita a validação do tipo de usuário. Mas, ao invés de chamar diretamente o repository, agora chama outro método dentro desta mesma classe, que por sua vez, agora se chama register() e recebe como parâmetro o array $attributes e uma instância de UserRepository.

Independentemente de qual dos repositories for passado como parâmetro, ambos são do tipo UserRepository e quando for chamado o método save(), irá chamar de acordo com o que foi passado.

Dessa forma, nós temos a chamada para o repository de uma forma mais centralizada, e mantendo o mesmo tipo de objeto na passagem do parâmetro.

Espero que tenha ficado claro para você. Caso contrário, comente aí sua dúvida.

E aí, gostou? No próximo artigo da série vou falar sobre o Interface Segregation Principle (Princípio da Segregação de Interface)

Dúvidas e feedbacks são sempre bem-vindos.

Top comments (7)

Collapse
 
ircmiguel profile image
Irc Miguel

Parabéns Lucas, muito bem explicado, no aguardo da continuação dessa série!

Collapse
 
lucascavalcante profile image
Lucas Cavalcante

Passei um tempo ausente da geração de conteúdo, mas estou retomando. :) Logo, logo terá post novo na continuação da série.

Collapse
 
lucascavalcante profile image
Lucas Cavalcante
Collapse
 
doug1n profile image
Douglas Gomes

Legal, mas não é uma boa prática o objeto Request sair do controller. Request é da camada http, um service autônomo teria que implementar um request para chamar esse Repository 😅.

Collapse
 
lucascavalcante profile image
Lucas Cavalcante

Fala Douglas! Tudo bem?

Você está coberto de razão. Eu sou um grande defensor de se manter requests e responses apenas no controller, já que essa é a principal responsabilidade dessa camada.

A maneira como implementei aí no exemplo foi para facilitar a didática, pois se tivesse que implementar um Request ou, ainda, fazer essa validação no próprio Controller, traria uma complexidade ao exemplo.

Mas, vou ficar mais atento para não ferir boas práticas, independente da didática abordada.

Obrigado pelo feedback.

Collapse
 
killme56k profile image
Leandro Alves

Muito simples a didática, espero que continue a série.

Collapse
 
lucascavalcante profile image
Lucas Cavalcante • Edited