Solid é a junção de cinco princípios básicos para a programação orientada a objetos. A intenção de todos estes é manter o seu código mais leaível, extensível, manuteníbel, flexível entre outras qualidades como ser facilimente refatorável. Esta junção foi primeiramente citada por C. Martin, também conhecido como Uncle Bob.
Princípio da responsabilidade única.
Uma clase deve possuir apenas um motivo para ser alterada. Isso indica que uma classe deve ter apenas uma responsabilidade dentro do sistema. Exemplo: Pulmões não devem bombear sangue.
Quando acumulamos várias funções ou motivos para existir numa classe, a fazemos mutável em mais de uma ocasião.E quanto mais aumentamos essa rede de responsabilidades aumentamos a área de impacto deste módulo no caso de um incidente. Uma classe com responsabilidade única é uma classe com alta coesão. Lembre-se: "acomplamento baixo e coesão alta".
Quando uma classe possui funções que não lhe deveriam ser suas obrigações conhecer, piora sua forma de logar, de fazer tracking de erro, de versionar e até de navegar no código. Não né, migx?
Close errado:
public void PagarSalário(int idEmpregado)
{
CalcularHorasDeTrabalho(idEmpregado);
CalcularHorasExtras(idEmpregado);
DescontarImpostos(idEmpregado);
EnviarTransferência(idEmpregado);
EnviarEmailDeConfirmacao(idEmpregado);
}Correto:
class Notificador
{
public void EnviarEmailDeConfirmacao()
Quando a fora de {
//Código
}
}
class Transferidor
{
public void EnviarTransferência()
{
//Código
}
}
class CalculadoraDeSalario
{
public void DescontarImpostos()
{
//Código
}
public void CalcularHorasExtras()
{
//Código
}
public void CalcularHorasDeTrabalho()
{
//Código
}
}No primeiro exemplo, quando precisarmos mudar a forma de calcular horas de trabalho mudar, seremos obrigados a mudar também a classe responsável por pagar salários. Também não quando não temos uma definição correta de responsabilidade, a classe se torna vulnerável, desnecessariamente, a erros que podem ser cometidos em próximos commits. Separando estas responsabilidades também conseguiremos fazer uma manutenção bem mais precisa no código.
Princípio de Aberto-Fechado
Classes devem ser abertas para extensão e fechadas para modificação. OU seja, se sua classe precisa de nova função, deve ser extendida uma nova funcionalidade sem necessidade de alterar o código já existente.
Extenção de uma classe é um dos princípios primeiros da orientação à objeto. É esperado que uma classe possa receber novas funcionalidades sem alterar as já presentes.
Close errado
class CalculadoraDeJuros
{
public decimal DescontoMensal(TipoDeConta tipoDeConta, decimal valor)
{
if (tipoDeConta == TipoDeConta.Física)
{
return valor * 1.1;
}
else if (tipoDeConta == TipoDeConta.Jurídica)
{
return valor * 1.3;
}
else
{
return valor;
}
}
}Correto
public abstract class Conta
{
public abstract decimal DescontoMensal (decimal valor);
}
public class ContaPessoaFisca : Conta
{
public override decimal DescontoMensal (decimal valor)
{
return valor * 1.1m;
}
}
public class ContaPessoaJuridica : Conta
{
public override decimal DescontoMensal (decimal valor)
{
return valor * 1.3m;
}
}No primeiro exemplo, dividido por IFs teríamos uma lista imensa e difícil de controlar neste fluxo quando chegarmos à um número grande tipos de contas. Especializando as classes, saberemos exatamente como cada uma delas funciona e se comporta sem precisar depender do fluxo de IFs pra saber onde que sua aplicação passará.
Princípio de substuição da Liskov
Se q(x) é uma propriedade demonstrável dos objetos x do tipo T. Então q(y) deve ser verdadeiro para objetos y de tipo S, onde S é um subtipo de T.
Esta máxima garante que heranças estão sendo usadas de forma correta. Ela é garantida quando uma classe pode ser substituída por uma subclasse e (com uso do polimorfismo) os resultados não se alteram.
Um exemplo claro disso é o seguinte paradigma: Todo quadrado é um retângulo pois possui quatro ângulos retos. Então pode-se dizer que quadrados são abstrações de retângulos. Até aí correto. O seu retângulo deve ter os métodos para setar lado e altura assim como o quadrado, correto? Errado! Ter dois métodos para definir os lados do quadrado é um erro pois todos os lados do quadrado medem o mesmo.
Close errado
public abstract class Retangulo
{
public int Altura {get; set;}
public int Comprimento {get; set;}
public abstract void SetAltura(int altura);
public abstract void SetComprimento(int comprimento);
}
public class Quadrado : Retangulo
{
public override void SetAltura(int altura)
{
if (altura > 0)
{
Altura = altura;
}
else
{
throw new ArgumentException("Altura deve ser maior que zero");
}
}
public override void SetComprimento(int comprimento)
{
if (comprimento > 0)
{
Comprimento = comprimento;
}
else
{
throw new ArgumentException("Comprimento deve ser maior que zero");
}
}
}Princípio da segregação de interface
Subclasses não devem precisar implementar métodos que ele não usa. Este princípio nos indica a segregar uma interface para que todas as suas implementações não tenham quem lidar com métodos desnecessários.
Por exemplo: uma calculadora de custos de plano de saúde não deve possuir um métodos de cálculo de custos para cuidados com a gravidez em sua classe raíz. Afinal clientes do sexo masculino não devem ser cobrados por esse serviço.
Close errado
public abstract class Animal
{
public abstract void Nascer();
public abstract void Crescer();
public abstract void Reproduzir();
public abstract void Morrer();
public abstract void Rugir();
}
public class Peixe : Animal
{
public override void Nascer()
{
Console.WriteLine("YES! Party!");
}
public override void Crescer()
{
Console.WriteLine("Glub glub!");
}
public override void Reproduzir()
{
Console.WriteLine("Põe os gametas alí no canto que eu ponho os meus já já.");
}
public override void Morrer()
{
Console.WriteLine("Snif snif. =(");
}
public override void Rugir()
{
Console.WriteLine("Ué!?");
throw new NotImplementedException();
}
}Correto
public abstract class Animal
{
public abstract void Nascer();
public abstract void Crescer();
public abstract void Reproduzir();
public abstract void Morrer();
}
public class Peixe : Animal
{
public override void Nascer()
{
Console.WriteLine("YES! Festa no oceano!!");
}
public override void Crescer()
{
Console.WriteLine("Crescendo as barbatanas bacanas!!");
}
public override void Reproduzir()
{
Console.WriteLine("Põe os gametas alí no canto que eu ponho os meus já já.");
}
public override void Morrer()
{
Console.WriteLine("Snif snif. =(";
}
public void FazerBlohas()
{
Console.WriteLine("GlubGlub");
}
}Este exemplo é bem explicito quando damos funções demais à classe animal e ela passa a ter funções que não são implementáveis em todas as suas implementações possíveis.
Princípio da inversão de dependência
HollyWood Principle: "Don't call us, we'll call you"
- Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações;
- Abstrações não devem depender de detalhes. Detalhes devem depender de abstrações.
Inverter a dependência faz objeto extensível e resiliente nas mudanças relacionadas à implementação. O cliente não é quabrado quando ocorre erro num detalhe da implementação. Permite o objeto a receber várias implementações.
Você não precisa que fiação fique soldada à sua lâmpada. Por isso desacoplamos a fonte de energia dos aparelhos que precisam de energia elétrica.
Close errado
public class BoloMaker
{
public void FazerBolo()
{
Ingredientes ingredientes = new Ingredientes();
Batedeira batedeira = new Batedeira();
Forno forno = new Forno();
ingredientes.AdicionaIngredientes();
batedeira.Bater(5);
forno.Assar(45);
Console.WriteLine("Bolo tá pronto!");
}
}
public class Ingredientes
{
public void AdicionaIngredientes ()
{
Console.WriteLine("Adiciona farinha, óleo e outras coisas.");
}
}
public class Batedeira
{
public void Bater (int velocidade)
{
Console.WriteLine($"Batendo na velocidade {velocidade.ToString()}.");
}
}
public class Forno
{
public void Assar (int minutos)
{
Console.WriteLine($"Assando por {minutos.ToString()} minutos.");
}
}Correto
public class BoloMakerCorreto
{
private readonly Batedeira _batedeira;
private readonly Ingredientes _ingredientes;
private readonly Forno _forno;
public BoloMakerCorrento(Ingredientes ingredientes, Batedeira batedeira, Forno forno)
{
_forno = forno;
_ingredientes = ingredientes;
_batedeira = batedeira;
}
public void FazerBolo()
{
_ingredientes.AdicionaIngredientes();
_batedeira.Bater(5);
_forno.Assar(45);
Console.WriteLine("Bolo tá pronto!");
}
}Neste exemplo fica claro que quem precisar de um BoloMaker escolherá qual melhor forno para o bolo, qual a batedeira melhor e a lista de ingredientes e dará isso tudo pronto ao BoloMaker. BoloMaker apenas precisa saber que vai receber estas entidade e como preparar o bolo.
https://robsoncastilho.com.br/2013/05/01/principios-solid-principio-da-inversao-de-dependencia-dip/
http://www.eduardopires.net.br/2013/05/single-responsibility-principle-srp/
http://deviq.com/single-responsibility-principle/
https://www.codeproject.com/Articles/703634/SOLID-Architecture-principles-using-simple-Csharp
https://stackoverflow.com/questions/56860/what-is-an-example-of-the-liskov-substitution-principle
http://williamdurand.fr/2013/07/30/from-stupid-to-solid-code/