Princípios SOLID: o que são e como aplicá-los no PHP/Laravel (Parte 02 - Aberto-fechado)
Dando continuidade à série de artigos sobre princípios SOLID, hoje vou falar sobre o segundo dos princípios.
Open-closed Principle
(Princípio Aberto-fechado)
Objetos ou entidades devem ser abertas para extensão, mas fechadas para modificação.
Em outras palavras, se eu tenho uma classe genérica que atende a diversos tipos de perfis, ao invés de sair fazer condicionais para cada caso, podemos criar especializações dessa classe, e em cada uma delas eu coloco o que atende aquele perfil.
Vamos continuar o exemplo que eu usei no artigo anterior, onde nós temos 2 tipos de User
, o Employee
e o Contractor
.
Você pode ver aqui que eu criei 2 Repositories, um para cada tipo de usuário, e em ambas eu criei o método save()
executando exatamente a mesma coisa. (no final do artigo eu expliquei que usei dessa maneira para facilitar o entendimento da separação de responsabilidades)
Então, vamos melhorar esse código.
class BaseRepository
{
protected $obj;
protected function __construct(object $obj)
{
$this->obj = $obj;
}
public function all(): object
{
return $this->obj->all();
}
public function find(int $id): object
{
return $this->obj->find($id);
}
public function findByColumn(string $column, $value): object
{
return $this->obj->where($column, $value)->get();
}
public function save(array $attributes): bool
{
return $this->obj->insert($attributes);
}
public function update(int $id, array $attributes): bool
{
return $this->obj->find($id)->update($attributes);
}
}
Criei uma classe BaseRepository
que como o próprio nome diz, serve como base para classes especialistas, contendo os métodos que são genéricos a todas as classes que irão estendê-la.
Vamos criar as classes filhas (ou especialistas).
class EmployeeRepository extends BaseRepository
{
protected $employee;
public function __construct(Employee $employee)
{
parent::__construct($employee);
}
}
class ContractorRepository extends BaseRepository
{
protected $contractor;
public function __construct(Contractor $contractor)
{
parent::__construct($contractor);
}
}
Se as classes EmployeeRepository
e ContractorRepository
não tiverem nenhuma especificidade, basta criá-las apenas com o construtor chamando a classe pai que todos os métodos irão funcionar. Na camada de Service quando formos fazer a injeção de dependência, injetamos a classe especialista.
Mas utilizando alguns princípios da orientação a objetos, vamos fazer algo específico para cada uma delas.
class EmployeeRepository extends BaseRepository
{
protected $employee;
public function __construct(Employee $employee)
{
parent::__construct($employee);
}
public function all(): object
{
$except = [3,17,22];
return $this->employee->whereNotIn('id', $except)->get();
}
}
class ContractorRepository extends BaseRepository
{
protected $contractor;
public function __construct(Contractor $contractor)
{
parent::__construct($contractor);
}
public function save(array $attributes, int $hoursPerWeek): bool
{
$attributes['hours_per_week'] = $hoursPerWeek;
return $this->contractor->insert($attributes);
}
}
Na classe EmployeeRepository
eu fiz uma sobreposição do método all()
onde quase tudo é igual à classe pai (nome, parâmetros, retorno) exceto a implementação.
Na classe ContractorRepository
eu fiz uma sobrecarga do método save()
onde o nome do método é o mesmo da classe pai, mas a quantidade e/ou tipo dos parâmetros é diferente.
Dessa forma consegui deixar ambas as classes mais especializadas sem interferir na classe pai, ou perder suas características.
Espero que tenha gostado. No próximo artigo da série vou falar sobre o Liskov Substitution Principle (Princípio da Substituição de Liskov)
Dúvidas e feedbacks são sempre bem-vindos.