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

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 o esqueleto de um jogo de tiro 2D visto de cima usando P5.js

Usando lógicas matemáticas como trigonometria para criar e calcular o esqueleto de um jogo de tiro 2D em javascript

Criando um jogo de guerra nas estrelas em javascript usando a biblioteca p5.js

Jogo simples de guerra espacial desenvolvido em javascript. Esse jogo usa cálculos de física para simular efeitos de atrito e inércia.

Desenvolvendo um jogo de quebra blocos em javascript

Programando um jogo clássico de arcade usando javascript e p5.js. O usuário deve quebrar os blocos utilizando uma bola ao mesmo tempo que evita que a bola saia pela parte inferior da tela

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.

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).

Veja também

Afinal, para que servem os cookies que sempre são solicitados quando entramos em uma página?

A política de privacidade é obrigatória para qualquer site que utiliza dados pessoais do usuário. Porém, quais dados são esses especificamente?

Nunca se sabe quando tem alguém nos espionando no nosso computador

Um computador conectado à internet está exposto a diversos perigos. O spyware é um deles e é esse malware responsável por roubar contas de redes sociais.

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.

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

POJO e Java Bean

Expressões utilizadas na linguagem Java para descrever classes simples que contém atributos, getters e setters de um objeto.

Vetores geométricos

Caracterizam uma grandeza física que possui módulo, direção e sentido. Pode simular eventos como queda, atração e deslocamento de objetos em um meio.