Adriano Lisboa Blog
Pensamentos, histórias e ideias.

OOD: SOLID para humanos (com exemplos)

Este é o primeiro artigo de uma série de 3 artigos sobre Object-Oriented Design (OOD) e foi baseado no livro Practical Object-Oriented Design: An Agile Primer Using Ruby da Sandi Metz, nos próximos artigos pretendo falar sobre DRY (Don't Repeat Yourself) e LoD (Law of Demeter).

Quando comecei a estudar OOP e OOD, SOLID foram os primeiros princípios que tive contato, na época não consegui achar nenhum artigo que fosse didático o suficiente para que um novato em programação pudesse aprender. Minha ideia com essa série de artigos é exemplificar os princípios dando exemplos de violações dos mesmos e como seria o seu correto uso. Os exemplos foram escritos em Ruby, mas servem para qualquer linguagem.


Afinal de contas, o que é SOLID?

SOLID é um acrónimo que foi introduzido por Michael Feathers, autor do livro Working Effectively with Legacy Code, mas que foi popularizado por Robert Martin (Uncle Bob) em meados dos anos 2000 em seu artigo Design Principles and Design Patterns. SOLID consistem em cinco princípios de design de software que ajudam com que o mesmo seja mais entendível, flexível e manutenível.

Os princípios são:

S – Single Responsibility Principle
O – Open-closed Principle
L – Liskov Substitution Principle
I – Interface Segregation Principle
D – Dependency Inversion Principle

Como Uncle Bob disse, SOLID são princípios, não regras.

The SOLID principles are not rules. They are not laws. They are not perfect truths. The are statements on the order of “An apple a day keeps the doctor away.” This is a good principle, it is good advice, but it’s not a pure truth, nor is it a rule. - Uncle Bob


Princípios

Single Responsibility Principle

Esse princípio diz que uma classe / objeto (ou até mesmo um método) deve conter apenas uma responsabilidade e que essa responsabilidade deve ser completamente encapsulada pela classe.

Quando falamos de uma única responsabilidade estamos falando de uma única razão para se mudar uma classe, se você tem várias responsabilidades você poderá ter várias razões para mudar uma classe.

Ganhos ao utilizar o princípio:

Exemplo de violação do princípio

Exemplo Single Responsibility Principle

Figura 1: Exemplo de violação do princípio de Single Responsibility - Cortesia: https://rubygarage.org/blog/solid-principles-of-ood

No exemplo acima a classe FinancialReportMailer é responsável por duas tarefas: gerar o relatório utilizando o método generate_report! e enviar o relatório utilizando o método send_report. Segundo o SRP essa classe deveria ser dividida em duas (exemplo abaixo).

Exemplo do correto uso do princípio

Exemplo Single Responsibility Principle
Figura 2: Exemplo do correto uso do princípio de Single Responsibility - Cortesia: https://rubygarage.org/blog/solid-principles-of-ood

Agora temos duas classes com responsabilidades especificas: uma de gerar o relatório e a outra de enviar o mesmo.


Open-Closed Principle

Esse princípio diz que classes, módulos, funções devem ser abertos para extensão, mas fechadas para modificação. Na prática o princípio diz que uma entidade poderá ter seu comportamento estendido sem modificar o seu código original.

Ganhos ao utilizar o princípio:

  • Looser Coupling Dependency entre as classes
  • Um código mais fácil de se ler
  • Menor risco de se quebrar uma funcionalidade já existente.

Exemplo de violação do princípio

Exemplo Open-Closed Principle
Figura 3: Exemplo de violação do princípio Open-Closed - Cortesia: https://www.monterail.com/hubfs/PDF content/SOLID_cheatsheet.pdf

No exemplo acima para novas maneiras de se fazer logs na aplicação a classe Logger deverá ser alterada.

Exemplo do correto uso do princípio

Exemplo Open-Closed Principle
Figura 4: Exemplo do correto uso do princípio Open-Closed - Cortesia: https://www.monterail.com/hubfs/PDF content/SOLID_cheatsheet.pdf

No exemplo acima várias classes que tratam logs de maneira diferente poderiam ser criadas, e a classe EventTracker já está preparada para lidar com elas dado que seu construtor poderá receber qualquer tipo.


Liskov Substitution Principle

Esse princípio foi definido por Barbara Liskov, e a sua ideia é que objetos devem poder ser substituídos por instâncias de seus subtipos sem que a funcionalidade seja alterada do ponto de vista do cliente.

Ganhos ao utilizar o princípio:

  • Validação de que suas abstrações estão corretas
  • Código facilmente reutilizável
  • Um código mais fácil de se ler
  • Hierarquias de classes mais fáceis de se entender

Exemplo de violação do princípio

Exemplo Liskov Substitution Principle
Figura 5: Exemplo de violação do princípio Liskov Substitution - Cortesia: https://www.monterail.com/hubfs/PDF content/SOLID_cheatsheet.pdf

No exemplo acima a classe Square herda de Rectangle e substitui o comportamento dos métodos de Rectangle.


Interface Segregation Principle

Esse princípio diz que classes que implementam interfaces não deveriam ser forçadas a implementar métodos que não vão utilizar. Ao perceber que uma interface está se tornando grande, uma boa ideia é "quebrar" essa interface em várias outras menores mais específicas ao domínio de sua aplicação.

Ganhos ao utilizar o princípio:

  • Código facilmente reutilizável
  • Um código mais fácil de se ler

Exemplo de violação do princípio

Exemplo Interface Segregation Principle
Figura 6: Exemplo de violação do princípio Interface Segregation - Cortesia: https://www.monterail.com/hubfs/PDF content/SOLID_cheatsheet.pdf

No exemplo acima a classe Drive não faz uso do método change_engine e a classe Mechanic não faz uso do método start_engine.


Dependency Inversion Principle

Módulos de alto nível (ex: lógica de negócios) não devem depender de módulos de baixo nível (ex: operações de banco de dados). Ambos devem depender de abstrações. Abstrações não devem depender de detalhes. Detalhes devem depender de abstrações.

Ganhos ao utilizar o princípio:

  • Código facilmente reutilizável
  • Um código mais fácil de se ler
  • Classes injetadas podem ser facilmente mockadas em testes

Exemplo de violação do princípio

Exemplo Dependency Inversion Principle
Figura 7: Exemplo de violação do princípio Dependency Inversion - Cortesia: https://www.monterail.com/hubfs/PDF content/SOLID_cheatsheet.pdf

No exemplo acima a classe ConsoleLogger (classe de baixo nível) é instanciada dentro da classe de alto nível EventTracker.

Exemplo do correto uso do princípio

Exemplo Dependency Inversion Principle
Figura 8: Exemplo do correto uso do princípio Dependency Inversion - Cortesia: https://www.monterail.com/hubfs/PDF content/SOLID_cheatsheet.pdf

No exemplo acima a classe ConsoleLogger (classe de baixo nível) injetada na classe de alto nível EventTracker.


Conclusão

Como Uncle Bob mesmo disse, SOLID são princípios, não regras. Então caso a caso deve ser estuado para entender se o princípio em si se aplica ou não. Na minha experiência com desenvolvimento de software a utilização de SOLID tem me ajudado a manter um código mais limpo, organizado, fácil de se manter, além de ajudar muito na definição se sua arquitetura.


Agradecimentos

Um agradecimento especial ao Brizeno por ter revisado o artigo :)


Referências

Author image
Brasil
I'm a Developer with good experience on design and architecture of software projects, agile methodologies, continuous integration, continuous deployment and continuous delivery.