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: 1918

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

Criando um jogo de pacman usando javascript e pixi.js (parte 1)

Desenvolvimento dos conceitos mais básicos do clássico pacman, como: mapa, animação, deslocamento e detector de colisões.

Criando um sistema de mini garagem automatizada integrada com um sistema de monitoramento independente

Desenvolvimento de um sistema de monitoramento que exibi todos os eventos que acontecem na garagem automatizada, como abertura de portões ou ocupação de vagas.

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.

Integrando o PHP com Elasticsearch no desenvolvimento de um sistema de busca

Projeto de criação de um sistema de busca usando o framework Symfony e Elasticsearch. A integração com Kibana também é feito de modo remoto com um raspberrypi.

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

A manutenção de softwares é o maior pesadelo de um programador quando o código parece um campo minado

Existe muitos programadores que escrevem algoritmos mal planejados. Essa falta de planejamento dificulta a manutenção do aplicativo e aumenta a probabilidade de novos bugs.

O usuário malicioso joga a isca e espera a vítima pacientemente

Phishing tem esse nome pois a vítima se torna só mais um peixe na rede. Ter conhecimento de phishing é o melhor jeito de evitar ser um desses peixes

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.

Arquitetura limpa - Clean Architecture

Eficiente quando aplicada em softwares de grande porte que necessitam de manutenção ao longo prazo. Criada em 2012 por Robert Martin.

Princípio de segregação de interfaces - Interface Segregation Principle

Os clientes não devem ser forçados a importar métodos que eles não usam. Os métodos devem ser segregados de modo abstrato em interfaces.

Classes na programação

Conjunto de variáveis e funções que podem ser encapsuladas em uma única unidade de instância e definir o escopo e a acessibilidade de cada elemento