DEV Community

Lucas Cavalcante
Lucas Cavalcante

Posted on

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (5)

Collapse
robsonpiere profile image
Robson Piere

Mais uma ótima explicação.
Uma dúvida: a classe BaseRepository poderia ser Abstrata ?

Collapse
lucascavalcante profile image
Lucas Cavalcante Author

Olá, Robson! Desculpe a demora para responder... por algum motivo, não recebi essa notificação.

Poderia ser abstrata sim, sem nenhum problema. Mas como eu quis focar aqui na questão da extensibilidade de uma classe, sem necessariamente falar de assinaturas de contratos de classes abstratas e/ou interfaces, aí acabei usando uma herança simples mesmo.

Obrigado pelo comentário. :)

Collapse
tiagodev profile image
Tiago Vieira

Ótimo conteúdo Lucas!

Collapse
kadudesouza profile image
Kadu de Souza

Muito bom, Lucas! Aguardando os próximos artigos.

Collapse
devinicius profile image
Vinicius Pereira de Oliveira

Conteúdo de ótima qualidade, isso abriu minha mente de um jeito incrível, muito obrigado Lucas por compartilhar um conteúdo tão importante