Solid - sabrinabm94/javascript GitHub Wiki

O SOLID trás os 5 princípios da programação orientada a objetos:

S — Single Responsiblity Principle

Princípio da responsabilidade única

Uma classe deve ter somente uma responsabilidade dentro do software, executando somente uma única tarefa, evitando assim as God Class (uma classe que faz tudo), facilitando assim a manutenibilidade do sistema no futuro.

class Order //Good Class
{
    public function calculateTotalSum(){/*...*/}
    public function getItems(){/*...*/}
    public function getItemCount(){/*...*/}
    public function addItem($item){/*...*/}
    public function deleteItem($item){/*...*/}
}

class OrderRepository
{
    public function load($orderID){/*...*/}
    public function save($order){/*...*/}
    public function update($order){/*...*/}
    public function delete($order){/*...*/}
}

class OrderViewer
{
    public function printOrder($order){/*...*/}
    public function showOrder($order){/*...*/}
}

A violação desse princípio resulta em

  • Falta de coesão: assumir mais responsabilidades que deve

  • Alto acoplamento: grande nível de dependência do sistema nessa classe, causando um alto risco de eventos colaterais ao fazer alterações nesta

  • Dificuldade de implementar testes automatizados: a criação de testes para essa classe se torna árduo e dificultoso

  • Baixo reaproveitamento de código: a classe está muito grande e específica, sendo dificil reaproveitar seu código nas demais partes do sistema

O — Open-Closed Principle

Princípio Aberto-Fechado

Objetos e classes devem estar abertos para serem estendidos mas fechados para modificações, ou seja, devemos extender uma classe para reutilizar sua estrutura nas classes filhas, mas a sua estrutura original não deverá ser alterada e sim somente complementada/alterada nas classes filhas que a reutilizam.

class hired
{
    public function payment()
    {
        //...
    }
}

class internship
{
    public function payment()
    {
        //...
    }
}

class payroll
{
    protected $value;
    
    public function calculate($employee)
    {
        if ( $employee instanceof hired ) {
            $this->value = $employee->payment();
        } else if ( $employee instanceof internship) {
            $this->value = $employee->payment();
        }
    }
}

A forma correta de implementar os diferentes tipos de contratações seria o seguinte:

<?php

interface Remunerative
{
    public function remuneration();
}

class hired implements Remunerative
{
    public function remuneration()
    {
        //...
    }
}

class internship implements Remunerative
{
    public function remuneration()
    {
        //...
    }
}

class payroll
{
    protected $value;
    
    public function calcular(Remunerative $employee)
    {
        $this->value = $employee->remuneration();
    }
}

Com isso evitamos criar funções desnecessárias para outras classes filhas que utilizam a classe pai ou até inserir bugs na classe principal que seria espelhado para todas as filhas.

L — Liskov Substitution Principle

Princípio da substituição de Liskov

Uma classe derivada (herdeira) deve ser substituível por sua classe base

class ParentClass 
{
    public function getName()
    {
        echo 'Parent Class';
    }
}

class ChildClass extends ParentClass 
{ 
    public function getName()
    {
        echo 'Child Class';
    }
}

$parent = new ParentClass;
$child = new ChildClass;

function showName(ParentClass $object)
{
    return $object->getName();
}

showName($parent); //Parent Class
showName($child); //Child Class

Tanto passando a classe pai quanto a classe filha como parâmetro, a função funciona da mesma forma pois tem a mesma estrutura, com valores e tipos similares. Com isso podemos usar o polimorfismo corretamente sem obter resultados inesperados.

I — Interface Segregation Principle

Princípio da Segregação da Interface

Uma classe não deverá ser forçada a implementar interfaces e métodos não necessários.

É melhor termos várias interfaces mais específicas do que uma única interface genérica que obrigue a implementar métodos desnecessários em quem as utilizam.

interface birds
{
    public function setLocalization($longitude, $latitude);
    public function setAltitude($altitude);
    public function render();
}

class Parrot implements birds
{
    public function setLocalizacao($longitude, $latitude)
    {
        //...
    }
    
    public function setAltitude($altitude)
    {
        //...
    }
    
    public function render()
    {
        //...
    }
}

class Pinguim implements birds
{
    public function setLocalizacao($longitude, $latitude)
    {
        //...
    }
   
    public function setAltitude($altitude)
    {
        //Penguins are flightless birds!
				//It shouldn't have this method implemented, forced by the interface
    }
    
    public function render()
    {
			//...
    }
}

O ideal seria separar essa interface em outras mais específicas para evitar a violação deste princípio:

interface birds
{
    public function setLocation($longitude, $latitude);
    public function render();
}

interface BirdsThatFly extends birds
{
    public function setAltitude($altitude);
}

class Parrot implements BirdsThatFly
{
    public function setLocation($longitude, $latitude)
    {
        //...
    }
    
    public function setAltitude($altitude)
    {
        //...   
    }
    
    public function renderizar()
    {
        //...
    }
}

class Penguin implements birds
{
    public function setLocation($longitude, $latitude)
    {
        //...
    }
    
    public function render()
    {
        //...
    }
}

D — Dependency Inversion Principle

Princípio da inversão da dependência

Dependa de abstrações e não de implementações

  1. Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender da abstração

  2. Abstrações não devem depender de detalhes. Detalhes devem depender de abstrações

No código abaixo temos um alto nível de acoplamento pois a classe PasswordReminder implementa o MySQLConnection, impedindo a classe PasswordReminder de ser reutilizada que não use o MySQL.

use MySQLConnection;

class PasswordReminder
{
    private $dbConnection;
    
    public function __construct()
    {       
        $this->dbConnection = new MySQLConnection();           
    }
    
    //...
}

Para evitar esse acoplamento, podemos criar uma interface para facilitar o processo:

interface DBConnectionInterface
{
    public function connect();
}

class MySQLConnection implements DBConnectionInterface
{
    public function connect()
    {
        //...
    }
}

class OracleConnection implements DBConnectionInterface
{
    public function connect()
    {
        //...
    }
}

class PasswordReminder
{
    private $dbConnection;

    public function __construct(DBConnectionInterface $dbConnection) {
        $this->dbConnection = $dbConnection;
    }
  
    //...
}

Com isso podemos utilizar a classe DBConnection qualquer tipo de banco de dados, tornando as classes MySQLConnection e OracleConnection desacopladas de dependendo da abstração com possibilidade de reutilização de código.