DEV Community

Cover image for SOLID - Guia Completo
Arthur Barboza
Arthur Barboza

Posted on

SOLID - Guia Completo

SOLID é um acrônimo para descrever alguns dos princípios apresentados por Robert C Martin (também conhecido como Uncle Bob) para sistemas desenvolvidos através do paradigma orientado a objetos (POO).

Tais princípios formam a base para um código bem estruturado, o qual permitiria uma manutenção mais fácil e facilitaria o desenvolvimento de novas funcionalidades e sua refatoração.

Estes princípios buscam apresentar como um desenvolvedor pode tirar um bom proveito dos recursos do POO, evitando o aumento da complexidade do sistema e promovendo a reusabilidade do código.

Single Responsability Principal

O Princípio da Responsabilidade Única consiste em atribuir apenas uma responsabilidade para uma classe, promovendo maior segmentação do sistema e facilidade para identificar a localização de determinado trecho de código. Além do mais, concentrar muito código em uma classe resulta em um código extremamente acoplado e dependente, dificultando a manutenção e aumentando a possibilidade de introduzir bugs.

Tal cenário também dificulta o planejamento e desenvolvimento de testes para o sistema.

Um software que não está de acordo com este princípio, apresenta em seu código fonte uma ou mais "God Classes", a qual se trata de um anti-pattern que descreve uma classe com inúmeras responsabilidades.

Open-Closed Principle

O Princípio Aberto-Fechado consiste em descrever um código que esteja Aberto para extensões e fechado para alterações. Em outras palavras, para o desenvolvimento de novas funcionalidades, devemos sempre incrementar através de novas classes, e não alterar as superclasses.

Isso implica em um software com um nível de abstração bem estruturado e capaz de utilizar de forma eficiente os recursos de OOP tais como : herança, interfaces, polimorfismo e encapsulamento.

Exemplo :


// Classe original alterada. Não segue o princípio

// Antes
class Empregado {

    private $salario;

    public function calcularSalario()
    {
        return $this->salario;
    }
}

// Depois
class Empregado {

    private $salario;

    private $comissao;

    public function calcularSalario($vendas)
    {
        return $this->salario + ($this->comissao * $vendas);
    }
}


// Classe original preservada e extendida, está seguindo o princípio

class Empregado {

    protected $salario;

    public function calcularSalario()
    {
        return $this->salario;
    }
}

class EmpregadoComissionado extends Empregado {

    private $comissao;

    public function calcularSalarioComComissao($vendas)
    {
        return parent::calcularSalario() + ($this->comissao * $vendas);
    }

    // Neste caso, além de facilitar a manutenção, o método original está sendo preservado e não sofre alterações diretas 

}

Enter fullscreen mode Exit fullscreen mode

Liskov Substitution Principle

O princípio da substituição de Liskov, implica que uma classe A a qual possui como subclasse a classe B, pode ser "substituída" pela classe B. Em outras palavras : uma subclasse deve ser compatível com os casos de uso da superclasse.

Exemplo :


class Empregado {

    protected $salario;

    protected $vendas;

    public function calcularSalario()
    {
        return $this->salario;
    }
}

class EmpregadoComissionado extends Empregado {

    private $comissao = 0.1;

    public function calcularSalario()
    {
        return parent::calcularSalario() + ($this->comissao * $this->vendas);
    }
}

function getEmpregadoSalario(Empregado $empregado){
    return $empregado->calcularSalario();
}


$empregado = new Empregado();
$empregadoComissionado = new EmpregadoComissionado();

echo getEmpregadoSalario($empregado);
echo getEmpregadoSalario($empregadoComissionado);

Enter fullscreen mode Exit fullscreen mode

Nesse caso acima, a classe EmpregadoComissionado herda de Empregado, e o uso de Empregado pode ser substituído por EmpregadoComissionado.

O princípio implica em inúmeros fatores, tais como visibilidade e assinatura de métodos, lançamento e tratativa de exceções, retornar valores de tipos diferentes de dados. Todos estes devem ser tratados para que o desenvolvedor garanta a conformidade com o princípio.

Este princípio garante a integridade do sistema e diminuí a possibilidade de surgir novos bugs inesperados.

Interface Segregation Principle

O Princípio da Segregação de Interface implica que uma classe não deve implementar uma interface a qual apresenta métodos que não serão utilizados.

Diferente do que por vezes ocorre, implementar uma interface e apenas apresentar a assinatura seguido pelo corpo vazio de alguns de seus métodos, não é o ideal e deve ser evitado ao máximo.

Para garantir que este erro não ocorra, é necessário que o desenvolvedor implemente interfaces mais específicas segmentando melhor cada comportamento para cada caso de uso e regra de negócio.

Dependency Inversion Principle

O princípio da inversão de dependência consiste em passar objetos externos como parâmetros para uma classe a qual dependa deles, através de uma abstração, evitando que a classe dependente mantenha alto acoplamento com relação à este objeto.

Suponha que eu tenha a classe Pedido e essa classe instancia a classe CartaoDeCredito no interior de um dos seus métodos, o ideal seria que a classe Pedido recebesse uma instância de uma abstração MeioDePagamento permitindo que Pedido não tenha uma dependência restrita a CartaoDeCredito.


// Errado, não está seguindo o princípio de inversão de dependência

class CartaoDeCredito{

    public function pagar()
    {
        return pagar('endpoint_do_gateway_de_pagamento');
    }
}


class Pedido {

    public function fecharPedido()
    {
        $cartaoDeCredito = new CartaoDeCredito();
        $cartaoDeCredito->pagar();
    }
}


// Correto, agora está em conformidade com o princípio

abstract class MeioDePagamento{

    abstract function pagar();
}


class CartaoDeCredito extends MeioDePagamento
{
    public function pagar()
    {
        return pagar('endpoint_do_gateway_de_pagamento');
    }
}


class Pedido {

    private $meioDePagamento;

    public function __construct(MeioDePagamento $meioDePagamento)
    {
        $this->meioDePagamento = $meioDePagamento;
    }

    public function fecharPedido()
    {
        $this->meioDePagamento->pagar();
    }

Enter fullscreen mode Exit fullscreen mode

Isso promove a independência da classe Pedido, uma vez que é possível utilizar qualquer instância da abstração MeioDePagamento, também facilitando o desenvolvimento de novas funcionalidades. A classe Pedido mantem contato apenas com uma abstração, e não possui responsabilidades ou mesmo conhecimento sobre detalhes de implementação.

Top comments (2)

Collapse
 
dotallio profile image
Dotallio

Really like how you break down each principle with clear examples - makes SOLID way less intimidating to apply. Have you found any one principle trickier to stick to in real projects?

Collapse
 
devarthurbarboza profile image
Arthur Barboza

Thank you for your feedback! Actually, I believe that understanding SOLID is the easier part, the hardest is for sure applying it in complex code bases.

Answering your question, I have found the Single Responsability a little bit tricky when it comes to refactoring (not developing the code), due to the amount of classes and new methods that could be resulted to this application. But this is a challenge resulting of the practice of refactoring, which should be done when the code contains code smells resulting of the lack of the Single Responsability application.

In the first moment it can be dificult to work with, but after done the code would be cleaner and easier to maintain.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.