S.O.L.I.D para leigos como eu

Jonas Rosendo
BRQ Tech
Published in
9 min readMar 22, 2020
Dia das bruxas foto criado por jcomp — br.freepik.com

Eae, cara!

Como você está ? Programando muito ? Eu sei…eu sei…não precisa chorar, vai dar tudo certo 🙄. Eu compreendo sua dor de abrir um código e estar mais bagunçado do que seu quarto. 😢

É bem provável que boa parte desta bagunça seja pela falta dos princípios ensinados pelo nosso querido Uncle Bob. Os princípios de SOLID. O quê !? Você nunca ouviu falar de SOLID ? 😮

Sem problemas, eu te explico. 😊

SOLID é um acrônimo mnemônico que nos ajuda a lembrar dos cinco princípios do OOD (Object-Oriented Design). Estes princípios são:

  • Single Responsibility Principle
  • Open-Closed Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle

Eu sei, parece muita coisa, mas não precisa ficar assustado e nem querer desistir da sua carreira. Usarei alguns exemplos enquanto apresento cada princípio pra você.

Single Responsibility Principle (SRP)

Este é nosso primeiro princípio. O Single Responsibility Principle nos diz que:

Uma classe deve ter um, e somete um, motivo para mudar.

Ou seja, pequeno gafanhoto, uma entidade de software (Classes, Métodos, Módulos e etc) deve ter apenas uma única funcionalidade dentro dela. Se houver mais de uma funcionalidade o princípio foi quebrado. Vamos para um exemplo prático:

Temos o seguinte método:

O método acima define qual será a cor do Sabre de Luz de acordo com a classe da pessoa. Se for um Jedi recebe a cor azul, se for um Sith recebe a cor vermelha e todos os outros recebem a cor verde (eu sei que não é assim, não me apedreje 🙄).

O problema é que nosso método está com mais de uma responsabilidade, porque ele compara a classe da pessoa e retorna a cor, assim ferindo o SRP, precisamos refatorar este método para que ele fique dentro do padrão SRP.

Criaremos outros dois métodos que terão uma única responsabilidade:

Note que os métodos acima, realmente, tem como responsabilidade apenas checar se a pessoa é de determinada classe. Estes métodos poderiam ser ainda mais genéricos fazendo com que pudéssemos quebrá-los em mais partes. Mas, por motivos de simplicidade e tempo, manterei desta maneira.

Agora faremos com que a classe da pessoa seja passada para os métodos que criamos anteriormente e ele irá dizer se é verdadeiro ou falso e em seguida, nosso método setLightSaberColor fará sua parte, que será apenas retornar a cor do Sabre de Luz sem ter que fazer comparações dentro dele mesmo como estava sendo feito na primeira versão do nosso método.

Este é o primeiro princípio espero que tenha conseguido compreender. Então vamos para nosso segundo princípio.

Open-Closed Principle

Nosso segundo princípio o OCP nos diz que:

Entidades de software (classes, módulos, funções, etc) devem ser abertos para extensão, porém, fechados para modificações.

Ué, como assim ? 🤨

Basicamente, pequeno gafanhoto, o OCP pede que escrevamos nossa classes, módulos e funções de maneira que, quando uma nova funcionalidade for adicionada, nós não deveríamos precisar modificar nada do código existente, e sim que nosso código antigo possa utilizar o novo sem problemas de compatibilidade.

Pois é, parece confuso, talvez um exemplo prático deixe o conceito um pouco mais claro. Vamos usar um exemplo bem simples introduzido pelo Uncle Bob.

Imagine que precisamos calcular a área de algumas formas geométricas, neste exemplo usarei o retângulo e o círculo. Então, criemos suas classes:

Agora criaremos nossa classe que controlará os cálculos:

Aqui podemos ver dois problemas na construção do método, consegue encontrá-los ?

Não encontrou ? Sem problemas, eu te conto. Primeiro, estamos quebrando o SRP porque nosso método está com mais de uma responsabilidade. E segundo, se precisarmos colocar uma nova forma geométrica, teríamos que alterar nosso método calculateArea com mais uma condicional e é neste momento que entra o OCP.

Para resolver este problema criaremos uma interface que será a base para todas as formas geométricas que tivermos:

Faremos que cada forma geométrica implemente nossa interface Shape. E sobrescreveremos o método area criado anteriormente, retornando o resultado do cálculo da área de acordo com a forma (cada classe cuidando do seu próprio cálculo).

Agora, atualizaremos nossa classe controladora passando o tipo da lista para Shape e chamando o método area para retornar o valor final do cálculo.

Compare a diferença de tamanho dessa nova versão do método com antiga. Surreal!!! 😮 Poderíamos acrescentar qualquer outra forma geométrica e não precisaríamos fazer nenhuma alteração no método já existente (desde que esta forma implemente Shape).

Acho que sobre OCP é tudo. Vamos para o próximo princípio!

Liskov Substitution Principle

Este princípio foi criado por nossa querida cientista Barbara Liskov (inclusive, recomendo fortemente a leitura de seus livros, são excelentes). O LSP nos diz:

Classes derivadas devem ser substitutíveis por suas classes base.

Basicamente, isso significa que, uma subclasse deve ter total compatibilidade com sua superclasse, ou seja, ela não deve quebrar os métodos já definidos pela classe pai. Eu sei, você gosta de exemplos 🙄 vou te dar um bem simples.

Imagine que temos uma classe que cria um retângulo e precisamos criar um quadrado a partir desta classe. E…(como todos nós sabemos 😏) um quadrado é um tipo de retângulo.

Então, temos a seguinte classe:

E criaremos um classe para o quadrado estendendo a nossa classe retângulo.

Neste momento, temos acesso a todos os métodos da classe Rectangle. Porém, se utilizarmos os mesmos métodos sem alterar nada, estaremos abrindo espaço para que seja criado um retângulo usando um quadrado, então precisamos fazer algumas alterações.

Primeiramente, precisamos sobrescrever os métodos setHeight e setLength, de modo que, qualquer um que seja chamado faça que altura e comprimento tenham o mesmo valor.

Não esqueça de alterar os atributos da classe Rectangle para protected, assim a classe Square conseguirá ter acesso à eles.

Nossa classe Square deverá estar assim:

Agora, posso ter uma variável de instancia do tipo retângulo e a partir dela posso substituí-la por um quadrado a qualquer momento. E não quebrará minha aplicação.

Resultados:

Esse foi um exemplo bem básico, mas, acredito que é suficiente para sentir um pouquinho do poder do LSP.

Agora, vamos para o próximo princípio.

Interface Segregation Principle

Este é um dos meus preferidos (junto com o DIP que veremos em breve), o ISP nos diz:

Crie interfaces refinadas que são específicas do cliente.

Traduzindo, não faça classes implementarem métodos que elas não usarão!

Ok, você quer um exemplo 🙄

No Android (meu bebê🥺) precisamos de Click Listeners, interfaces que ficam “ouvindo” os cliques que são recebidos em determinados componentes.

Poderíamos fazer algo mais ou menos assim:

Quando esta interface fosse implementada, a classe que a recebeu sobrescreveria seus métodos e só alegria! SQN!

Acredito que você concordará comigo que nem todos os componentes são selecionáveis ou possuem alguma ação com um clique longo.

Dependendo do caso, a classe que implementasse esta interface, não utilizaria alguns destes métodos e os mesmos ficariam em branco (má prática, nunca faça isso! 😡).

E é neste momento que entra nosso grande amigo ISP 😊

Devemos separar cada método em sua própria interface

É claro que você pode e deve variar as combinações dos métodos como deixa o clique longo junto com o clique rápido, não há nenhum problema em ter mais de um método dentro de uma interface, porém, seu implementadores precisam utilizar todos, NADA DE MÉTODOS EM BRANCO! 😇

Agora, finalmente, chegamos em nosso último, porém, não menos importante…

Dependency Inversion Principle

Se você chegou até aqui, acredito que realmente que aprender a usar o SOLID, o que é muito bom! O DIP é muito semelhante ao OCP.

A principal regra do DIP é:

Dependa de abstrações, não de concreções.

Ou se você preferir algo mais detalhado:

– 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.

Exatamente isso! Não deixe que uma classe seja dependente de outra, se uma classe realmente precisa depender de outra classe, faça com que ambas dependam de uma mesma interface.

Sim, vamos para o exemplo:

Imaginemos que precisamos criar um sistema de iluminação de LED em um cômodo de uma casa.

Criaremos um trecho de código que representará um interruptor.

Aqui temos um problema que fere gravemente o princípio DIP, a classe LightSwitch, depende da classe concreta LedLamp. Se houver alguma alteração na classe LedLamp, poderemos ser forçados a fazer grandes alterações em nossa classe LightSwitch.

Mas, como evitar isso ?

Está é a mágica do DIP. Para isso criaremos um interface que irá intermediar a dependência entre as classes.

Agora, precisamos fazer que nossa classe LedLamp implemente nossa nova interface e sobrescreva os métodos, também precisaremos colocar uma variável de instancia da nossa interface na classe LightSwitch. Assim, criaremos uma “ligação” entre as classes sem que uma dependa diretamente da outra, porém, ambas dependendo de uma abstração.

LedLamp implementa LightService e sobrescreve seus métodos, note que temos um método que retorna o estado da lâmpada.

LightSwitch recebe uma instancia de referencia da nossa interface, agora temos acesso aos métodos e estados da nossa lâmpada. Ou seja, ao clicarmos no interruptor, ele chamará a interface responsável pelo evento que chamará a classe que está responsável por tratar este evento de acordo com a necessidade.

Conclusão

SOLID deixará seu código mais limpo, flexível e conciso. Use e abuse do seus princípios e veja os benefícios que ele trará.

Eu acho que é isso…este é o básico da coisa. Espero que não tenha ficado confuso(a). É o primeiro artigo que escrevo, então se houver algum erro corrija-me. Obrigado, abs!!!

E você tem alguma dica para acrescentar ? 😊

--

--