Princípios SOLID aplicados ao Android

Como melhorar o design do seu código com esses 5 princípios

Arildo Borges Jr
CodeInLab
5 min readJul 12, 2021

--

Photo by Artem Kniaz on Unsplash

Overview

Você já ouviu falar sobre princípios SOLID? Eles estão diretamente ligados ao desenvolvimento orientado à objetos, que é o paradigma mais utilizado para desenvolvimento mobile.

Vamos entender cada um desses princípios agora:

S - Single Responsability Principle - SRP

Criado por Robert C. Martin, também conhecido como Uncle Bob, esse princípio, como o próprio nome diz, está relacionado a parte de responsabilidades de uma classe ou função.

Para que um código atenda esse princípio, é necessário que todas as responsabilidades estejam bem definidas. Uma única função não deve ter diversos objetivos não correlacionados, assim como uma classe também não deve ter mais de um propósito. Isso garante um código altamente coeso e com maior granularidade.

Vamos ver na prática, utilizando a função onBindViewHolder() da classe RecyclerView.Adapter , muito utilizada no Android:

exemplo 1 - violando o SRP
exemplo 2 - adequado ao SRP

Note que no exemplo 1, estamos violando o conceito de responsabilidade única, pois a responsabilidade da função onBindViewHolder() deveria ser apenas de conectar os dados à sua respectiva view. Porém estamos também agregando lógica para formatação de datas.

Conformando ao SRP, chegamos ao exemplo 2, onde extraímos essa lógica para uma função auxiliar, numa classe de utils. Agora estamos respeitando esse princípio e além disso, diminuindo duplicação de código, pois provavelmente precisaremos formatar datas em outros pontos do app, e agora é só utilizar a função formatDate() .

O - Open-Closed Principle - OCP

A premissa desse princípio é a seguinte:

Classes devem ser abertas à extensões, mas fechada à modificações.

Partindo dessa afirmação, podemos concluir que uma classe pode ser estendida, pode ter sua funcionalidade aumentada através de sua classe derivada, porém não pode ter sua funcionalidade alterada. Vamos ver um exemplo:

exemplo 1 - está violando o OCP
exemplo 2 - está em conformidade ao OCP

Note que no exemplo 1, estamos intencionalmente violando o OCP, pois a classe Circle() está estendendo a classe Rectangle() e alterando sua funcionalidade, pois agora ela não sabe mais calcular a área de um retângulo, apenas de um círculo.
No exemplo 2, estamos conformando o código ao OCP, agora estamos estendendo de uma classe mais genérica, a Shape() , e escrevendo a lógica específica em cada uma das classes derivadas. Dessa maneira garantimos que não haja alterações na funcionalidade da classe base.

L - Liskov Substitution Principle - LSP

Esse princípio foi introduzido por Barbara Liskov, em 1987. Sua definição é a seguinte:

Se q(x) é uma propriedade demonstrável dos objetos x de tipo T. Então q(y) deve ser verdadeiro para objetos y de tipo S onde S é um subtipo de T.

A partir dessa definição, podemos dizer que uma classe derivada deve poder ser substituída por sua classe base, assim como uma classe base deve poder ser substituída por sua classe derivada.

exemplo 1 - está violando o LSP

A classe base deve funcionar como uma abstração de suas classes derivadas. Note que no exemplo acima, temos a classe base Vehicle() e as classes derivadas Car() e Bicycle() .

Então a implementação está certa, né? Afinal, tanto carro, quanto bicicleta são tipos de veículos… A resposta é Não!

De acordo com o LSP, essa implementação está incorreta pois, como vimos anteriormente, uma classe base deve poder ser substituída por sua classe derivada e vice-versa, porém nesse exemplo, isso não é verdade. Nossa abstração não está corretamente implementada, visto que não podemos substituir Bicycle() por Vehicle() , pois bicicleta não tem motor, logo não temos como implementar a função startEngine() .

Vamos refatorar esse código, a fim de implementar corretamente a abstração da classe base:

exemplo 2 - em conformidade com o LSP

Note que agora temos duas classes base intermediárias. Elas servem para nos ajudar nessa especificidade da abstração. Agora, é possível substituir a classe derivada pela classe base, sem nenhum problema e, dependendo do tipo utilizado, teremos acesso às funções específicas que cada uma implementa. Agora sim, estamos em conformidade ao LSP.

I - Interface Segregation Principle - ISP

Quem nunca passou pela situação de precisar implementar apenas uma função de uma interface, mas ser obrigado a colocar as outras funções "vazias" no código? Criado pelo Uncle Bob, esse princípio fala exatamente sobre isso:

Nenhum cliente deve ser forçado a depender de métodos que não utiliza.

Temos um exemplo clássico disso no Android, a interface TextWatcher, utilizada para observar mudanças em campos de texto:

exemplo 1 - está violando o ISP

Na maioria das vezes, precisamos implementar apenas uma dessas funções da interface, mas ela nos obriga a ter o override de todas as três funções.

Para conformar essa interface ao ISP, o ideal seria refatorar a própria interface, mas como ela faz parte da SDK do Android, isso não é possível.
Nesse caso, podemos criar nossa própria classe customizada, implementando a TextWatcher, de modo que possamos usar apenas a função necessária:

exemplo 2 - está em conformidade com o ISP

Note que criamos uma abstração da TextWatcher, a MyTextWatcher . Nessa abstração, nós fazemos uma implementação vazia das três funções e, a partir de agora, utilizamos sempre essa abstração, no lugar da TextWatcher. Isso permite que eu implemente apenas a função que eu realmente preciso, sem repetir código desnecessário no projeto :D.

D - Dependency Inversion Principle - DIP

Também criado pelo Uncle Bob, o Princípio da Inversão de Dependência pode ser definido pelas seguintes premissas:

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

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

Partindo disso, vamos ver um exemplo clássico de violação do DIP:

exemplo 1 - está violando o DIP (adaptado de https://springframework.guru/principles-of-object-oriented-design/dependency-inversion-principle)

Se você rodar esse código, verá que ele é totalmente funcional, porém, conceitualmente, ele está quebrando o DIP, pois, um módulo de alto nível, o ElectricPowerSwitch , depende de um módulo de baixo nível, o Light .

Vamos refatorar esse código, de modo que, além de funcional, ele também esteja em conformidade ao DIP:

exemplo 2 - está em conformidade com o DIP (adaptado de https://springframework.guru/principles-of-object-oriented-design/dependency-inversion-principle)

Criamos duas interfaces, para representar as funcionalidades de cada classe. Dessa forma, ElectricPowerSwitch não depende mais da implementação da classe Light , mas sim de sua abstração. Tanto que nada impede de criarmos a classe Speaker , implementando a interface Switchable e usar ela dentro da ElectricPowerSwitch , sem precisar de nenhuma alteração.

Ahh, veja que nesse momento, também adequamos nosso código ao Princípio de Substituição de Liskov. Demais né? :D.

Por hoje era isso, pessoal! Qualquer dúvida ou sugestão, fique a vontade em comentar. Abração e até a próxima!

Referências

--

--