Skip to content

Instantly share code, notes, and snippets.

@olivatooo
Last active November 21, 2020 03:21
Show Gist options
  • Save olivatooo/38edc8be6a5c5e0d3c34a04669f8e723 to your computer and use it in GitHub Desktop.
Save olivatooo/38edc8be6a5c5e0d3c34a04669f8e723 to your computer and use it in GitHub Desktop.
Trabalho feito para disciplina de POOA

SOLID

Gabriel Olivato RA: 743537

Igor Magollo RA: 743550

(S)ingle Responsability Principle

O Single Responsability Principle , SRP, afirma que um componente de um software (no geral uma classe) deve possuir uma única responsabilidade. Isto significa que uma classe deve ter a responsabilidade de fazer uma (e apenas uma) tarefa bem feita, e como consequência disso quando queremos alterar esta funcionalidade alteramos apenas quem é responsável por ela.

Se o domínio do problema mudar, então a classe que resolve aquele problema deve ser atualizada. Se nós precisamos fazer alterações a esta classe, por algum motivo diferente, significa que aquela classe possui muitas responsabilidades.

No mundo python, existe um princípio de design chamado de pythonic way, este princípio de design nos ajuda a construir abstrações mais coesas; objetos fazem uma e apenas uma tarefa, seguindo a filosofia Unix. O que nós queremos evitar em todos os casos são objetos com múltiplas responsabilidades (geralmente chamados de god-objects, pois sabem muito mais do que deveriam saber).

"Quando menor a classe, melhor" - Algum Alemão que eu não achei o nome

Uma classe com muitas responsabilidades

Vamos supor um caso de uma aplicação que está encarregada de ler informações de uma fonte (podem ser arquivos de log, databases, or outras fontes) e identificar quais ações ele deve tomar para cada tipo de evento. Uma maneira (errada) de se fazer isso é:

class SystemMonitor:    
    def load_activity(self):        
        """Pega os eventos para serem processados."""
    def identify_events(self): 
        """Faz o parse dos dados e identifica para que vão ser usados"""
    def stream_events(self):        
        """Envia os eventos identificados para algum lugar externo."""

O problema com esta classe é que ela define uma interface com um conjunto de métodos que são ortogonais: cada um pode ser realizado independente do resto.

Esta falha de design cria uma classe rígida, inflexível e sujeita a erros pois é difícil de se manter. Neste exemplo, cada método representa uma responsabilidade da classe. Cada responsabilidade mostra uma razão pela qual a classe precisa ser modificada.

Considere o método load_activity, que recupera as informações de um local. Independentemente de como ele for implementado, é claro que ele possui sua própria sequência de passos, por exemplo: conectar a um banco de dados, parsear em uma formato esperado, e por aí vai. Se alguma parte disso mudar (por exemplo, trocar a estrutura de dados em que guarda os dados), a classe SystemMonitor precisa ser alterada. Isso faz sentido? Alterar o objeto SystemMonitor porque alteramos a maneira de representação dos dados? A reposta é não.

Os mesmos argumentos se aplicam aos outros métodos. Se nós mudarmos como nós identificamos os eventos, ou como nós enviamos os dados para um local externo, nós vamos fazer alterações na mesma classe.

Ou seja, a classe apresentada é de difícil manutenção. A solução então é criar abstrações menores e mais coesas.

Distribuindo responsabilidades

Para criar uma solução mais sustentável, nós separamos cara método em uma classe diferente. Desta forma, casa classe tem apenas uma responsabilidade:

class ActivityReader():
	def load(self, src):
		"""Carrega os dados de uma fonte"""

class SystemMonitor():
	def identify_event(self, data):
		"""Identifica um tipo de evento a ser carregado dado um tipo de dado"""

class Output():
	def stream(self, event):
		""" Envia um evento para uma saída"""

class AlertSystem():
	activity_reader = ActivityReader()
	system_monitor = SystemMonitor()
	output = Output()

	def run(source):
		data = activity_reader.load(source)
		event = system_monitor.identify_event(data)
		output.stream(event)

O mesmo comportamento pode ser alcançado por usar um objeto que vai interagir com instâncias das novas classes criadas, a ideia se mantém de que cada classe encapsula um conjunto de métodos que são independentes do resto. A ideia é de que, se ocorrer alguma alteração em alguma dessas 3 classes, não vá causar nenhum impacto no resto, e todas elas tem um objetivo bem claro do que estão fazendo. Se precisamos trocar a meneira de como carregamos os dados a classe AlertSystem não vai nem saber que essas alterações ocorreram.

As alterações são locais, o impacto na aplicação é mínimo, e cada classe é mais fácil de ser mantida.

Criando essas novas classes temos não só um código com melhor manutibilidade, mas também classes reusáveis. Imagine que agora, em outra parte da aplicação, temos que também ler atividades de logs, mas para um fim diferente. Com este design, nós podemos simplesmente utilizar objetos do tipo ActivityReader. Neste atual design isto faz sentido, mas no design (errôneo) demonstrado anteriormente não, pois a classe definida carregaria métodos extras que não seriam utiizados (identify_events() e stream_events())

Um importante esclarecimento é de que utilizando este princípio não significa que cada classe deve ter apenas um método. Classes podem e vão ter mais métodos, contanto que correspondam a lógica da classe se manter responsável apenas por uma tarefa.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment