DEV Community

Cover image for Princípio da Responsabilidade Única e as implicações na Orientação a Objetos e Arquitetura de Software
Victor Lima Reboredo
Victor Lima Reboredo

Posted on • Edited on

Princípio da Responsabilidade Única e as implicações na Orientação a Objetos e Arquitetura de Software

Introdução

O paradigma orientado a objetos é amplamente difundido no mundo de desenvolvimento de software. Através dele conseguimos simular de maneira eficiente as modularidades de um processo no mundo real, com reaproveitamento de código e aplicando métodos e padrões que tornam o código manutenível, eficiente e mais limpo e direto.

Coesão

Dentre os diversos conceitos aplicados na Orientação a Objetos, um ótimo conceito para se aprofundar é a coesão. A coesão é um princípio extremamente útil na programação (mesmo se utilizamos outro paradigma ao programar, como o estruturado, por exemplo). Através da coesão conseguimos organizar o código de maneira a deixá-lo mais limpo e esteticamente bem estruturado.

Em seu livro "Orientação a Objetos: Aprenda seus conceitos e suas aplicabilidades de forma efetiva", o professor Thiago Leite e Carvalho define coesão como um conceito que:

"[...] preconiza que cada unidade de código deve ser responsável somente por possuir informações e executar tarefas que dizem respeito somente ao conceito que ela pretende representar."

Podemos aplicar a coesão através das classes e dos relacionamentos associativos das mesmas. Criar unidades de código coesas com classes e associações contribui para definição de blocos de códigos responsáveis somente por tarefas e conceitos às quais elas se propõem. (Thiago Leite e Carvalho, pg. 23)

Classes coesas são extremamente importantes em sistemas orientados a objetos. São mais fáceis de dar manutenção, são compostas por menos códigos e podem ser mais facilmente reutilizadas no software. Elas também são menos propensas a defeitos e bugs.

Single Responsability Principle

O SRP ou também Princípio da Responsabilidade Única é o primeiro pilar do solid e existe para que possamos aplicar a coesão no nosso código.
Ele nada mais é do que a parte prática de tudo o que vimos até aqui, portanto colocamos em prática a coesão através do SRP. Ele preconiza que:

Uma classe deve ter uma, e apenas uma, razão para mudar.

É um tanto complexo no dia a dia do programador, termos bem definido um grupo de regras que irão de certa forma definir o conceito de uma classe coesa e de uma classe não-coesa. Os conceitos que podem rodear isso acabam por ser subjetivos. Acredito que o principal a se verificar em uma análise de classe é quantos métodos diferentes ela tem/quão grande elas são e quantos comportamentos diferentes é possível encontrar nela. Quanto mais métodos, código e comportamentos uma classe tiver, mais ela se tornará uma candidata ao título de "Classe não-coesa".

Exemplo

Neste exemplo, criarei a classe User. Ela será responsável por salvar um usuário em meu sistema, validar o e-mail e enviar um e-mail de boas-vindas para o usuário.

class User {
  constructor(public name: string, public email: string) {}

  saveToDatabase() {
    console.log(`Saving user ${this.name} to the database...`);
  }

  sendWelcomeEmail() {
    console.log(`Sending welcome email to ${this.email}...`);
  }

  validateEmail() {
    console.log(`Validating email ${this.email}...`);
    return this.email.includes("@");
  }
}

const user = new User("John Doe", "john.doe@example.com");

if (user.validateEmail()) {
  user.saveToDatabase();
  user.sendWelcomeEmail();
}
Enter fullscreen mode Exit fullscreen mode

Nossa classe está totalmente errada! Ela tem 3 tarefas totalmente distintas a que deveriam ser independentes. Se alguma lógica de validação do e-mail ou do banco de dados mudar, teremos que modificar a classe inteira, que deveria apenas representar a entidade Usuário. Criar testes unitários para testar a classe se torna uma dor de cabeça, já que devemos ter teste para cada método com lógicas distintas.

Aplicando o SRP...

class User {
  constructor(public name: string, public email: string) {}
}

class UserRepository {
  save(user: User) {
    console.log(`Saving user ${user.name} to the database...`);
  }
}

class EmailService {
  sendWelcomeEmail(user: User) {
    console.log(`Sending welcome email to ${user.email}...`);
  }
}

class UserValidator {
  validateEmail(user: User): boolean {
    console.log(`Validating email ${user.email}...`);
    return user.email.includes("@");
  }
}

const user = new User("John Doe", "john.doe@example.com");
const validator = new UserValidator();
const repository = new UserRepository();
const emailService = new EmailService();

if (validator.validateEmail(user)) {
  repository.save(user);
  emailService.sendWelcomeEmail(user);
}
Enter fullscreen mode Exit fullscreen mode

Agora, cada classe tem uma única responsabilidade. User apenas modela a entidade, UserRepository lida com o armazenamento no banco de dados, EmailService cuida do envio de emails, e UserValidator se concentra na validação.

Desta maneira podemos modularizar melhor qualquer mudança/dependência nas validações, isto é, se a validação mudar basta mudar um local do código. Se for necessário incluir mais campos na entidade de usuário e incluir uma validação para este novo campo, toda a edição no código ficará mais fácil!

Aplicando SRP em outras frentes

Um ponto importantíssimo no mundo de Software é a arquitetura que aplicamos. Pensando nesse princípio, um padrão arquitetural que vem à minha mente é o MVC (Model - View - Controller).

Esse modelo de software segrega as responsabilidades de um software monolítico em 3 frentes:

  • Model: É o domínio de nossa regra de negócio. São classes que representam tabelas de banco de dados ou entidades bem definidas. Através delas podemos gerenciar repositório de dados, fazer consultas e manipular dados.

  • View: É a camada que define a parte de interface de usuário da aplicação. Nela estarão os elementos gráficos interativos onde o cliente irá requisitar, manipular e deletar dados. É a parte de front.

  • Controller: É a camada responsável por ligar o "core" do software ao cliente (usuário, seja via app mobile, desktop ou web). Ele pode capturar uma requisição - seja via REST, GraphQL, gRPC e muitos outros - e converter tudo isso em uma ação dentro da aplicação.

Existem no mercado diversos frameworks/libs que utilizam o MVC como arquitetura, os principais e mais completos - na minha opinião, é claro - são: Laravel (PHP), Express (JavaScript), ASP.Net Core MVC (C#), Spring (Java) e Django (Python).

Conclusão

O Princípio da Responsabilidade Única é muito mais do que uma regra teórica: é uma prática essencial para quem busca escrever um código orientado a objetos robusto e escalável. Ao aplicar o SRP, garantimos que nossas classes se mantenham coesas, focadas em uma única responsabilidade, o que facilita a manutenção, testes e futura evolução do sistema.

A coesão, como discutido, é um dos pilares para alcançar da boa arquitetura de software. Quando uma classe possui uma única responsabilidade, ela se torna mais fácil de entender, de modificar e de estender, permitindo que o código evolua de maneira mais natural conforme as necessidades do projeto mudam. Além disso, a aplicação do SRP reduz a chance de bugs, pois minimiza o impacto das mudanças ao isolar responsabilidades.

Adotar o SRP desde o início de um projeto pode parecer um desafio, mas os benefícios a longo prazo superam em muito o esforço inicial. Projetar classes pequenas e bem definidas contribui para a criação de um sistema modular, onde cada parte pode ser desenvolvida, testada e mantida independentemente. Isso não apenas melhora a qualidade do código, mas também promove uma maior colaboração entre equipes de desenvolvimento, permitindo que diferentes partes do sistema evoluam em paralelo sem interferências indesejadas.

Em suma, o SRP não é apenas um princípio de design, mas uma prática que influencia diretamente a qualidade e a sustentabilidade do software que desenvolvemos. Incorporá-lo em nossa abordagem à programação orientada a objetos é um passo fundamental para construir sistemas que sejam não apenas funcionais, mas também fáceis de manter e adaptar ao longo do tempo.

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more