Princípio da responsabilidade única - Single Responsibility Principle

Princípio que diz que um módulo só deve mudar por um único motivo. Esse motivo pode ser o conteúdo de um módulo ou os atores que dependem dele.

Categoria de Programação

Postado em 14 abril 2022

Atualizado em 02 junho 2023

Palavras-chave: srp,ocp,single,responsibility,principle,clean,architecture,classe,programacao

Visualizações: 1945

Com o surgimento de linguagens orientadas a objetos, programadores passaram a ter um novo grande problema: dependências desnecessária entre classes. A dependência entre classes em si, não é a origem do problema. O verdadeiro problema é como o código é organizado. É muito comum encontrarmos uma classe com várias funções úteis com o nome “Utilidades” em alguns sistemas. Porém, essa prática não é recomendável, porque muitas classes vão começar a usar essas funções e depender da classe “Utilidades”. Quando uma classe tem muitas dependências, ela acaba se tornando estável (difícil de modificar), pois existem muitas classes dependendo dessa classe. O princípio da responsabilidade única (Single Responsibility Principle) surge como uma solução para esse problema.

O que é o princípio de responsabilidade única (SRP)?

Esse princípio foi criado por Robert Martin e ganhou relevância após a publicação da arquitetura limpa. O princípio de responsabilidade única foi publicado pela primeira vez em 2003 em um artigo chamado “Os princípios de OOD” (The Principles of OOD) por Robert Martin.

Esse princípio aborda que uma classe só deve ter uma razão para mudar. Mas pelo fato de uma classe só ter uma razão para mudar, isso não significa que ela deve ter apenas uma função. Além disso, é bastante comum desenvolvedores chegarem a conclusão que esse princípio fala que uma classe só pode fazer uma coisa. Porém, o próprio Robert diz que isso não é verdade. No livro de 2018 sobre a arquitetura limpa, Robert trouxe uma nova descrição sobre esse princípio:

Um módulo deve ser responsável por apenas um ator

O módulo se refere ao código-fonte (source code). E o ator se refere a classe que usa esse módulo. Em outras palavras, o que Robert quer dizer com esse princípio está por trás das dependências entre classes.

um modulo deve ser responsavel apenas por um ator

Veja na imagem acima um exemplo de violação do princípio de responsabilidade única. O exemplo acima é uma violação de SRP porque dois atores diferentes estão compartilhando o mesmo módulo. O módulo “Enviar mensagem” possui apenas uma função: enviar mensagem.

Por que não podemos compartilhar o mesmo módulo entre atores diferentes?

Compartilhar uma mesma função entre atores diferentes pode resultar em uma duplicação acidental. A duplicação acidental pode acontecer quando a função “Enviar mensagem” muda em favor de apenas um dos atores. O grande problema aqui é que o modo de um humano e um alienígena de mandar mensagens é diferente. Logo, uma modificação no módulo acaba fazendo com que um dos atores dependentes da função “Enviar mensagem” quebre ou tenha comportamentos estranhos.

Como aplicar o princípio de responsabilidade única?

Quando esse princípio diz que um módulo só deve ter uma responsabilidade, ele também se refere as dependências do módulo. Se olharmos apenas a classe de modo isolado e esquecermos das dependências, dificilmente saberemos por quantos motivos ela realmente pode mudar. O SRP diz que um módulo só deve fazer uma coisa, fazer bem e fazer apenas isso. Em resumo, devemos olhar dentro (o que uma classe faz) e fora (dependências entre classes) de um módulo.

Uma solução para o exemplo do humano e do alienígena seria separar o módulo em dois.

como aplicar o principio da responsabilidade unica

Separando os módulos por ator resolve o problema de duplicação acidental e traz mais algumas vantagens:

  • Diminui o acoplamento entre classes
  • Fortalece o princípio de segregação de interfaces
  • Evita a estabilidade de uma classe

Diminui o acoplamento entre classes

A diminuição do acoplamento entre classes se refere as dependências que usam o mesmo módulo. Se um módulo tiver 2 dependências, ele terá duas razões para mudar, se um módulo tiver 3 dependências ele terá 3 razões para mudar. Conforme o número de atores aumenta, o acoplamento também aumenta. Esse tipo de acoplamento torna o código frágil e robusto, pois esse módulo deve atender a 3 tipos de atores diferentes. Se tivermos apenas um único ator usando esse módulo, teremos muito mais facilidade para modificar o conteúdo.

Fortalece o princípio de segregação de interfaces

O princípio de segregação de interfaces diz que uma classe não deve depender de coisas que ela não usa. Se vários atores compartilham um mesmo módulo, mais chances esse módulo tem de abrigar conteúdo desnecessário para determinados atores. Se dividirmos cada módulo por ator, evitamos esse a importação de conteúdo desnecessário.

Evita a estabilidade de uma classe

A estabilidade se refere a robustez de uma classe. Quanto mais dependências, mais motivos essa classe tem para não mudar. É bastante comum ver classes voláteis (classes que mudam com frequência) sendo usadas por diversos atores em um sistema. Isso pode ser perigoso, pois quando uma classe muda ela tem grandes chances de influenciar as dependências.

Desvantagens do princípio de responsabilidade única

Se há uma desvantagem nesse princípio, essa desvantagem é ter que localizar cada classe para utilização. Uma consequência de usar o SRP é que as classes ficam segregadas e bem menos extensas. Robert diz que para resolver essa problema poderíamos usar Facades. Facades são classes especializadas em instanciar classes.

facades principio da responsabilidade unica

Assim, o ator “Humano” pode localizar e instanciar todas as classes que ela precisa apenas usando a classe “HumanFacade”.

Atores diferentes podem compartilhar o mesmo módulo desde que eles não tenham comportamentos diferentes?

Mesmo se os atores tem o mesmo comportamento, não é recomendado compartilhar o mesmo módulo, porque esses atores podem mudar por motivos diferentes. Esse princípio serve como uma proteção antecipada para que atores não tenham o seu comportamento influenciado pela modificação de outro ator.

Aplicando o princípio da responsabilidade única na prática

Como vimos no exemplo acima, apenas olhando para o conteúdo dentro de um módulo é difícil saber todos os motivos para ele mudar.

class MessageSender {
    Protocol protocol;
    String ipAddress;
    int portNo;
	
	public boolean sendMessage(String message) {
		if (!isValidAddress) {
			throw new Exception("Endereco IP inválido");
		}

		return protocol.sendMessage(message);
	}
	
	private boolean isValidAddress () {
		return protocol.check(ipAddress + ":" + portNo);
	}
}

Avaliando pelo nome da classe, temos a impressão que essa classe é compartilhada entre atores. Na maioria das vezes essa impressão é precisa.

MessageSender messageSender = new MessageSender();
...
Alien alien = new Alien(messageSender);
alien.sendMessage("Hello World");
Human human = new Human(messageSender);
human.sendMessage("Hello Mars");

Dois atores diferentes compartilham a mesma classe “MessageSender” quando instanciados, violando o princípio de responsabilidade única. Imagine que o ator alienígena precisa fazer uma mudança no modo que ele manda a mensagem, sendo obrigado a mudar o conteúdo “MessageSender”. Essa modificação teria grandes chances de afetar o comportamento do humano, que também depende dessa classe. Se dividirmos cada módulo por ator, eliminamos esse tipo de preocupação.

Alien alien = new Alien(new AlienMessageSender());
alien.sendMessage("Hello World");

Human human = new Human(new HumanMessageSender());
human.sendMessage("Hello Mars");

Esse é apenas um exemplo de solução para que estejamos de acordo com o SRP. Os princípios SOLID nos mostram outras abordagens para esse tipo de problema, como o princípio aberto fechado. Ao aplicarmos o princípio fechado, podemos mudar o comportamento de um módulo estendendo ele. Isso é possível graças as abstrações.

Conclusão

O princípio de responsabilidade única diz que um módulo só deve fazer uma coisa e bem feita. Porém, é difícil dizer quantas coisas um módulo faz olhando apenas o conteúdo dentro dele. É necessário conferirmos os atores que dependem desse módulo para dizer quantos motivos ele tem para mudar.

Projetos práticos

Desenvolvendo o campo de visão de um personagem em um plano 2D

Detectando objetos que entram dentro do campo de visão do personagem. Útil para servir de "gatilho" para eventos em um jogo.

Usando dados fornecidos pelo TSE para simular o gráfico das eleições presidenciais de 2022

Simulação dos gráficos do segundo turno das eleições presidenciais, utilizando python e ferramentas de análise de dados, pandas e jupyter.

Implementando um algoritmo de pathfinding

Implementando um programa que encontra a menor distância entre dois pontos dentro de um labirinto usando o algoritmo A* (a-estrela).

Tutorial de programação do jogo da serpente em javascript

Programando o clássico jogo da serpente usando o framework p5.js. Tutorial indicado para iniciantes da programação que querem aprender os conceitos básico da área criando jogos.

Criando um sistema de integração contínua (CI/CD)

Fazendo a integração contínua de Jenkins, Sonatype Nexus, Sonatype, JUnit e Gradle para automatizar processos repetitivos. Prática bastante usada em tecnologias de DevOps.

Veja também

Uma das vantagens de ter um robô trabalhando para você é o aumento da produtividade

Hoje em dia não é mais necessário gastar o nosso tempo com tarefas que podem se automatizadas. Os robôs estão aqui para nos ajudar...

A biometria digital é uma grande promessa ao futuro da tecnologia

Muitos serviços já utilizam a autenticação biométrica integrada com a inteligência artificial para melhorar a experiência do usuário, além de melhorar a segurança.

Pilha (stack) e fila (queue)

Pilha e fila são tipos de estrutura de dados que contribuem para um gerenciamento de dados mais inteligente e eficaz na programação

Notação do big-O

A notação do O grande é um método de fácil implementação, usado para avaliar a eficiência de um algoritmo em relação ao tempo de processamento.

Princípio da inversão de dependências - Dependency Inversion Principle

Ao ser aplicado permite que os detalhes passem a depender de abstrações, respeitando a direção da regra de dependências.

CSR Corporate Social Responsibility

CSR é a sigla para responsabilidade social corporativa. Essa expressão descreve práticas que contribuem para o bem da sociedade.