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);
}
}
}
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
}
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);
}
}
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.