Swift: Princípio da Responsabilidade Única

Compartilhe

Compartilhar no facebook
Compartilhar no google
Compartilhar no twitter
Compartilhar no linkedin

Se você é um desenvolvedor ou desenvolvedora, e possui um certo interesse pela área de engenharia de software, muito provavelmente já ouviu falar de um termo chamado SOLID [1]

Caso você nunca tenha ouvido falar, não se preocupe, esse artigo irá introduzir um pouco a respeito desse acrônimo e qual sua relação com o desenvolvimento de software. Além disso, ele irá focar também em como aplicá-lo no desenvolvimento iOS, utilizando-se da linguagem Swift.

O que significa SOLID?

O termo SOLID é um acrônimo para cinco princípios de Design de Programação, que possuem o intuito de facilitar a compreensão e o desenvolvimento, e também aumentar a flexibilidade e manutenibilidade de um software. 

A teoria de seus princípios foi introduzida por Robert C. Martin (também conhecido como “Uncle Bob”) [2], um instrutor e engenheiro de software americano, em seu paper Design Principles and Design Patterns, no ano de 2000.

Como já mencionado, SOLID é um acrônimo, ou seja, cada uma de suas letras corresponde à inicial de cada um dos princípios ao qual esse acrônimo diz respeito. 

A letra “S” se refere a Single Responsibility Principle (Princípio da Responsabilidade Única), a letra “O” diz respeito a Open-Closed Principle (Princípio de Aberto-Fechado), “L” corresponde ao termo Liskov Substitution Principle (Princípio da Substituição de Liskov), “I” remete ao termo Interface Segregation Principle (Princípio da Segregação de Interface), e por fim a letra “D” é referente a Dependency Inversion Principle (Princípio da Inversão de Dependência).

Nessa série de artigos serão detalhados o que cada um dos princípios significa, como implementá-los, como identificar quando uma implementação não os respeita e também suas vantagens.

Serão utilizados alguns exemplos simples, com o intuito de facilitar o entendimento e também essa explicação, mas fique ciente que esses princípios se aplicam para projetos de larga escala e códigos complexos, onde realmente é possível a notar a importância de segui-los e respeitá-los.

Nesse primeiro artigo será apresentado o primeiro dos princípios do SOLID, o Single Responsibility Principle.

Single Responsibility Principle (SRP)

O Princípio da Responsabilidade Única diz que “Uma classe deve ter somente uma única razão para ser alterada, ou em outras palavras, uma classe deve possuir responsabilidade única”. 

Mas afinal, o que é uma responsabilidade e como saber se uma classe possui ou não somente uma?

Uma responsabilidade pode ser considerada como um papel que uma classe é responsável por executar, mas uma melhor definição, quando se tratando desse princípio, seria que uma responsabilidade é, basicamente, uma razão para mudar.

Isso quer dizer que se for possível pensar em mais de um motivo para alterar uma determinada classe, é porque essa classe está assumindo mais de uma responsabilidade.

Exemplo em Swift

Por exemplo, considere o cenário abaixo, onde se é apresentado uma classe Square, uma classe GeometricGraphics e um protocolo Persistence.

Oi1

Oi2

Oi3

Pode-se observar primeiramente um protocolo Persistence, que somente é a abstração de uma eventual implementação de uma classe que seja responsável por salvar um objeto do tipo Square.

Nesse exemplo, não precisaremos tratar da classe concreta que implementaria essa lógica, até porque isso não virá ao caso.

Em seguida, é apresentada a implementação concreta da classe Square, que nada mais é do que a representação de um quadrado.

Essa classe possui um método de inicialização que recebe um objeto de persistência e também um inteiro que representa o valor do lado desse quadrado.

Além disso, ela contém três métodos: um que calcula a área do quadrado, um que salva o quadrado com o auxílio da persistência, e outro que salva o quadrado somente se sua área for maior do que 20.

Por fim, existe uma classe GeometricGraphics, que é responsável por desenhar um quadrado em uma interface visual, mas aqui novamente não iremos implementar essa lógica, visto que não é o intuito do exemplo.

Problemas em quebrar o SRP

Esse exemplo acima demonstra um cenário onde o SRP é violado, dado que a classe Square apresenta mais de uma responsabilidade a de calcular área, ou seja, uma responsabilidade geométrica, e, ao mesmo tempo, uma responsabilidade de persistência, ou seja, de salvar um objeto de seu tipo.

Utilizando-se da própria definição apresentada anteriormente, a classe Square está contra o princípio de responsabilidade única pelo fato de que possui dois eixos de mudanças.

O primeiro problema dessa responsabilidade dupla é que uma eventual mudança na estrutura de persistência (por exemplo, na assinatura do método save(_ square: Square) no protocolo Persistence) irá impactar em uma alteração também na classe Square.

Como a estrutura GeometricGraphics depende de Square, após as alterações terem sido feitas, essa estrutura terá de ser rebuildada, e, em alguns cenários, também retestada. Isso é ruim pelo fato de que aconteceria mesmo com a classe GeometricGraphics não dependendo de nada relacionado à persistência de Square.

Isso pode não parecer um problema muito sério para esse exemplo em si, e realmente não é. Mas imagine um cenário onde as estruturas em questão são módulos extremamente complexos e com diversas implementações.

Não seria legal ter que rebuildar e retestar um módulo à toa, seria?

Um segundo problema decorre do fato dessas duas responsabilidades também estarem acopladas. O método saveAccordingToArea() persiste um objeto dependendendo de sua área.

Esse acoplamento é ruim porque mudanças em uma das responsabilidades podem afetar a outra, e vice-versa, e o mesmo problema já destacado de recompilação e reteste pode acontecer novamente.

Além disso, esse acoplamento de responsabilidades é muito comum e fácil de acontecer quando se tem mais de uma responsabilidade dentro de uma classe.

Por fim, um terceiro e não menos importante problema é que possuir mais de uma responsabilidade irá dificultar e muito a testabilidade dessa estrutura, além de também prejudicar a leitura e o entendimento de seu código.

Esses problemas identificados no exemplo acima, embora se refiram ao contexto do exemplo, são problemas genéricos, e portanto identificados em qualquer implementação que contenha responsabilidades não únicas.

Respeitando o SRP

Existem inúmeras maneiras de se refatorar o código acima de forma que ele respeite o princípio da responsabilidade única. 

Uma delas seria separar a classe Square em duas novas classes: SquareGeometrics e SquarePersistence. A primeira seria responsável por conter o método area() e a segunda seria responsável por conter o método save().

Além disso, o método saveAccordingToArea() não estaria em nenhuma das duas, e seria responsabilidade de uma terceira classe, ou da própria estrutura que anteriormente estivesse chamando esse método.

Uma outra maneira seria criar dois protocolos, ao invés de duas classes concretas, SquareGeometricsProtocol e SquarePersistenceProtocol. Esses dois protocolos iriam conter os métodos area() e save(), respectivamente.

Por fim, a classe Square, que ainda existiria, seria responsável por implementar esses dois protocolos, e portanto implementar os dois métodos.

Essa abordagem iria resolver o problema, dado que com isso seria possível apenas importar/usar um dos protocolos, caso uma estrutura somente se utilize de uma das responsabilidades, o que é o acontece abaixo com a classe GeometricGraphics, que utiliza-se somente do SquareGeometrics.

Basicamente fazer isso é seguir uma abordagem muito próxima a de um Design Pattern muito conhecido: Facade

Oi4

Oi5

Oi6

Com certeza poderia-se também fazer, ainda, um pouco melhor, separando as implementações concretas. Ao invés de criar-se uma única classe Square, poderiam ser criadas duas classes diferentes, cada uma implementando um dos protocolos SquareGeometrics e SquarePersistence.

Considerações Finais

Embora este seja o princípio mais simples em teoria, é um dos mais difíceis de se colocar em prática. Isso acontece porque é realmente difícil identificar o que é uma responsabilidade, e quando uma classe está recebendo mais de uma delas.

Com tempo e prática, e também observando e estudando boas implementações que o seguem, é possível ir aperfeiçoando o uso desse princípio.

Deve-se atentar ainda ao fato de que, em alguns casos, uma classe possui dois eixos de mudança, ou seja, duas responsabilidades, mas mesmo assim a separação não é necessária.

Esse cenário pode ocorrer quando conclui-se que uma mudança em somente um desses eixos muito raramente irá ocorrer, ou que mesmo que ela eventualmente ocorra, isso não iria causar um efeito de recompilação ou de alteração em outras estruturas que a utilizam e que somente dependam da responsabilidade inalterada.

Dessa forma, separar essas duas responsabilidades seria uma complexidade desnecessária, e portanto não recomendada.

Por fim, um exercício sempre válido e recomendado é reservar uma pequena quantidade de tempo antes ou após ter desenvolvido um certo código, e refletir se o SRP foi ferido.

Se sim, é muito saudável pensar se existe alguma vantagem em fazê-lo em relação às vantagens que o princípio proporciona, e tentar medir se esse “trade-off” é válido ou não.

Resumo SRP

  • Esse princípio diz que classes devem possuir uma única responsabilidade, ou seja, somente um único eixo de mudança.
  • Garantir essa condição para uma classe é positivo pelos seguintes fatos:
    1. Isso evitará recompilações, retestes e “refactorings” desnecessários, que podem acontecer quando se tem uma classe com responsabilidade dupla e que precisa ter uma de suas responsabilidades alteradas. Se existir uma outra classe que utiliza dessa primeira classe, porém somente usufrui da responsabilidade inalterada, ela terá que ser recompilada e retestada com essa alteração. O que não aconteceria caso as responsabilidades fossem separadas.
    2. Isso ajudará manter um software com maior legibilidade e testabilidade, onde se é possível encontrar e entender facilmente a responsabilidade de cada unidade de código.
  • Esse princípio não é muito simples de se aplicar. Também não é muito fácil notar quando ele está sendo violado. O tempo e a experiência com certeza irão ajudar a desenvolver um olhar apurado e tomar as melhores decisões de design em um projeto quanto a ele.

Referências

  1. https://en.wikipedia.org/wiki/SOLID
  2. https://en.wikipedia.org/wiki/Robert_C._Martin
  3. Robert C. Martin. Design Principles and Design Patterns, 2000

Esse foi o primeiro artigo da série de cinco artigos sobre SOLID e seu uso em Swift. Espero que tenham gostado e sintam-se livres para deixar feedbacks, sugerir melhorias ou até mesmo me enviar uma mensagem!

Rodrigo Máximo

Rodrigo Máximo

Rodrigo Noronha Máximo Computer Engineer at Unicamp iOS Developer at Movilepay

Deixe um comentário

Categorias

Posts relacionados

Siga-nos

Baixe nosso e-book!

%d blogueiros gostam disto: