No início do presente século o cientista da computação Michael Feathers criou o acrônimo S.O.L.I.D. baseando-se na abordagem de Robert C. Martin acerca de arquitetura e desenvolvimento de software. Basicamente, essa importante “sopa de letrinhas” tornou-se fundamental para a engenharia de aplicações profissionais por representarem cinco princípios que deixam o código muito mais legível, organizado, desacoplado e refatorável, viabilizando – com isso – sua evolução e manutenibilidade. Vamos, portanto, verificar a seguir o significado de cada uma das letras dentro do contexto das melhores práticas para aqueles que desenvolvem aplicações em linguagens que suportam programação orientada a objetos, do inglês “Object Oriented Programming” (OPP).
S – “Single Responsability Principle” (Princípio da Responsabilidade Única)
Este é um dos mais importantes dentre os cinco princípios, e diz que uma classe ou método deve ter responsabilidade específica, ou seja, uma única tarefa ou ação a executar. Isso pode garantir que diante da necessidade de se fazer ampliações nessas entidades estaremos praticamente isentos de injetarmos pequenos “bugs” de forma inconsciente, o que poderia ser diferente ao modificarmos uma ”god class”, que são classes que fazem de tudo, onde a tarefa de se alterar uma funcionalidade sem afetar as demais pode ser extremamente árdua. Observe abaixo que a classe “DebitoContaCorrente” viola o princípio da responsabilidade única quando acumula um método para validar saldo, outro para debitar na conta e um último para emitir comprovante. Como conseqüência teremos um alto acoplamento, baixa coesão, dificuldade de ampliação da classe e impossibilidade de implementação de testes automatizados.
/**********************************************************
* Single Responsability Principle - Violando o Princípio
**********************************************************/
public class DebitoContaCorrente
{
public void ValidarSaldo(float valor) { }
public void DebitarConta(float valor) { }
public void EmitirComprovante() { }
}
Uma forma adequada de se escrever um código com as mesmas funcionalidades, acatando o princípio “SRP”, é mostrada a seguir. Veja que agora temos três classes e cada uma delas respeita uma especificidade.
/**********************************************************
* Single Responsability Principle - Acatando o Princípio
**********************************************************/
public class DebitoContaCorrente
{
public void DebitarConta(float valor) { }
}
public class SaldoContaCorrente
{
public void ValidarSaldo(float valor) { }
}
public class ComprovanteContaCorrente
{
public void EmitirComprovante() { }
}
O – “Open-Close Principle” (Princípio Aberto-Fechado)
Este segundo princípio requer que os objetos ou entidades estejam abertos para ampliações e fechados para modificações. Em outras palavras, significa afirmar que uma entidade deve ser concebida de forma a permitir o incremento de novos recursos ou comportamentos sem a necessidade de alteração do código original. No exemplo a seguir temos uma classe para calcular o desconto que cada operadora concederá aos clientes na fatura mensal do cartão de crédito. Repare que essa classe não obedece ao princípio do “OCP”, pois se precisarmos incluir mais operadoras teremos que alterar a rotina incrementado proporcionalmente outras estruturas condicionais; com isso, estaríamos expostos ao risco da incerteza de injetarmos eventuais “bugs” em um algoritmo que anteriormente estava funcionando.
/**********************************************************
* Open-Close Principle - Violando o Princípio
**********************************************************/
public class CartaoCredito
{
private string _BandeiraCartaoCredito;
public sting BandeiraCartaoCredito { get; set; }
public double CalculaDesconto(double valorFatura)
{
if (_TipoCartaoCredito.Equals("Bandeira_A")) { }
else if (_TipoCartaoCredito.Equals("Bandeira_B")) { }
}
}
Vimos que é extremamente relevante considerarmos a teoria de “Uncle Bob” desde o princípio da elaboração do nosso código de forma a construirmos nossas classes utilizando abstrações; dessa maneira, não serão necessárias alterações do conteúdo já existente para incremento de novas variações, tão somente faremos a ampliação de métodos na classe. A seguir temos um exemplo de como poderia ser essa prática.
/**********************************************************
* Open-Close Principle - Acatando o Princípio
**********************************************************/
abstract class CartaoCredito
{
public abstract double CalculaDesconto(double valorFatura);
}
class CartaoBandeora_A : CartaoCredito
{
public override double CalculaDesconto(double valorFatura) { }
}
class CartaoBandeira_B : CartaoCredito
{
public override double CalculaDesconto(double valorFatura) { }
}
class CartaoBandeira_C : CartaoCredito
{
public override double CalculaDesconto(double valorFatura) { }
}
L – “Liskov Substitute Principle” (Princípio da Substituição de Liskov)
Este princípio foi introduzido no final da década de 80 por uma cientista da computação chamada Barbara Liskov. Sua afirmação é de que uma classe derivada deve necessariamente poder ser substituída por sua classe base, sem que seja necessário alterar o código. Um exemplo clássico que pode ser citado para ilustrar este princípio seria a criação de uma sub-classe que calcula a área de um quadrado, sendo que essa tem como base uma classe que calcula a área de um retângulo. Embora ambos sejam quadriláteros geométricos o quadrado tem a particularidade de que seus lados precisam ser iguais; logo, o cálculo da área implementado na classe base (Retângulo) não poderia ser utilizado da forma como foi concebido, sendo necessário sobrescrevê-lo na classe filha (Quadrado) para acrescentar uma exceção caso os valores dos parâmetros referentes ao lado sejam diferentes. Uma possível solução seria a escrita de uma classe base que atendesse ao cálculo dos dois quadriláteros, ou seja, uma classe que poderia se chamar quadrilátero e simplesmente calcularia a área retornando o produto de: base * altura; sendo assim, outras duas classes derivadas dessa implementariam suas respectivas particularidades sem a necessidade de sobrescrever a classe base. Para ilustrar melhor essa possibilidade, observe o código a seguir.
/**********************************************************
* Liskov Substitution Principle - Acatando o Princípio
**********************************************************/
public abstract class Quadrilactero
{
public Paralelogramo(int largura, int altura)
{
Largura = largura;
Altura = altura;
}
public int Altura { get; private set; }
public int Largura { get; private set; }
public int Area { get { return Altura * Largura; } }
}
public class Quadrado : Quadrilactero
{
public Quadrado(int largura, int altura) : base(largura, altura)
{
if (largura != altura) throw new Exception("Erro: largura e altura diferentes!");
}
}
public class Retangulo : Quadrilactero
{
public Retangulo(int largura, int altura) : base(largura, altura) { }
}
I – “Interface Segregation Principle” (Princípio da Segregação de Interface)
O quarto princípio preconiza que é melhor termos várias interfaces especificas do que uma interface genérica. No exemplo hipotético abaixo temos a interface “ICompra” sendo implementada pelas classes “CompraPresencial” e “CompraOnLine”. Este código viola o princípio de “ISP” devido ao fato de que a classe “CompraPresencial” não implementa o método “PagarComCartao”, visto que ela não precisa desse método; dessa forma, a referida classe viola o “ISP” ao implementar um método que não utiliza.
/**********************************************************
* Interface Segregation Principle - Violando o Princípio
**********************************************************/
class Vendas
{
public interface ICompra
{
void Comprar();
void PagarComCartao();
}
public class CompraPresencial : ICompra
{
public void Comprar() { }
public void PagarComCartao() {throw new NotImplementedException();}
}
public class CompraOnline : ICompra
{
public void Comprar() { }
public void PagarComCartao() { }
}
}
Uma opção adequada para corrigirmos isso seria criarmos mais interfaces a fim de evitarmos a implementação de métodos desnecessários dentro das demais classes. Como está representado no exemplo abaixo.
/**********************************************************
* Interface Segregation Principle - Acatando o Princípio
**********************************************************/
class Vendas
{
public interface ICompra
{
void Comprar();
}
public interface IPagamentoComCartao
{
void PagarComCartao();
}
public class CompraPresencial : ICompra
{
public void Compra() { }
}
public class CompraOnline : ICompra, IPagamentoComCartao
{
public void Comprar() { }
public void PagarComCartao() { }
}
}
D – “Dependence Invertion Principle” (Princípio da Inversão de Dependências)
E poor último, mas não menos importante, temos o princípio da inversão de dependência. Este princípio tem o objetivo de reduzir o acoplamento entre as classes fazendo com que os módulos de alto nível não dependam de módulos de baixo nível, mas que ambos dependam de abstrações. Também, que as abstrações não dependam dos detalhes, e sim os detalhes dependam das abstrações. No exemplo abaixo ambas as classes – “PushButton” e “Led” – são classes concretas; portanto, a classe “PushButton” está fortemente acoplada à classe “Led”. Devido a isso, qualquer mudança ocorrida na classe “Led”, por mais simples que seja, fará com que “PushButton” pare de funcionar.
/**********************************************************
* Invertion Dependence Principle - Violando o Princípio
**********************************************************/
public class PushButton
{
private Led _led;
public void Acionar()
{
if (input) _led.Ligar();
else _led.Desligar();
}
}
Para atender este último princípio, devemos fazer de forma que a implementação dependa da abstração. Na prática, vamos inverter a dependência de “PushButton” para a classe “Led” fazendo com que ambas passem a depender de uma abstração, que no exemplo abaixo é representada pela interface “Dispositivo”.
/**********************************************************
* Invertion Dependence Principle - Acatando o Princípio
**********************************************************/
public class
{
private Dispositivo _dispositivo;
public void Acionar() {
if (input) _dispositivo.Ligar();
else _dispositivo.Desligar();
}
}
public interface Dispositivo
{
void Ligar();
void Desligar();
}
Conclusão
Como foi exposto neste artigo, o S.O.L.I.D. é um conjunto de princípios fundamentais em arquiteturas de softwares profissionais, pois eles tem a capacidade de fazer com que as aplicações fiquem muito mais robustas, escaláveis e flexíveis, de forma a proporcionar enorme facilidade à refatorações e às ampliações. Além disso, existem também outras excelentes práticas de programação, tais como: “Clean Code”, “Designer Parttners” e “Padrões de Arquiteturas de Software”, que reforçam a escrita de códigos consistentes por isso são regularmente praticados pelos “Seniors Devops Engineers”. Mas esses assuntos serão tópicos para futuras abordagens.
Saiba mais
Webinar Gravado: Arquitetura de Software para Sistemas Embarcados
Webinar Gravado: Metodologias de Desenvolvimento de Software Embarcado









