SOLID na prática — S: Single Responsibility Principle

Entenda o que é o Single Responsibility Principle (SRP), por que ele é mal interpretado pela maioria dos devs e como aplicá-lo com exemplos reais em C#. SRP não é sobre fazer uma coisa só. É sobre ter um único motivo para mudar. Entenda a diferença e veja como isso muda a forma de estruturar seu código.

27 de abril de 20264 min de leituraTawan Silva
SOLID na prática — S: Single Responsibility Principle

Compartilhe este post

Envie para alguém ou salve o link para ler depois.

LinkedIn WhatsApp

SOLID na prática — S: Single Responsibility Principle

O SRP é o princípio mais citado e mais mal interpretado do SOLID.

A definição que todo mundo aprende é: "uma classe deve fazer apenas uma coisa". Mas isso é vago demais para ser útil. Uma classe que salva um usuário no banco "faz uma coisa". Uma classe que valida, salva e manda e-mail também pode ser descrita como "fazendo uma coisa" — gerenciar usuários.

A definição correta, de Robert Martin, é outra:

Uma classe deve ter apenas um motivo para mudar.

Isso muda tudo. Não se trata de quantidade de métodos ou linhas de código — se trata de quantas fontes de mudança distintas podem forçar você a modificar aquela classe.


O problema: classe com múltiplos motivos para mudar

// essa classe tem pelo menos 3 motivos para mudar:
// 1. regras de negócio de usuário mudam
// 2. formato do e-mail muda
// 3. estrutura do banco de dados muda

public class Usuario
{
    public string Nome { get; set; }
    public string Email { get; set; }

    public Usuario(string nome, string email)
    {
        Nome = nome;
        Email = email;
    }

    // motivo 1: lógica de persistência
    public void Salvar(SqlConnection connection)
    {
        var cmd = new SqlCommand(
            "INSERT INTO Usuarios (Nome, Email) VALUES (@Nome, @Email)",
            connection
        );
        cmd.Parameters.AddWithValue("@Nome", Nome);
        cmd.Parameters.AddWithValue("@Email", Email);
        cmd.ExecuteNonQuery();
    }

    // motivo 2: lógica de e-mail
    public void EnviarBoasVindas()
    {
        var client = new SmtpClient("smtp.gmail.com", 587);
        var mensagem = new MailMessage(
            "[email protected]",
            Email,
            "Bem-vindo!",
            $"Olá, {Nome}! Seja bem-vindo."
        );
        client.Send(mensagem);
    }

    // motivo 3: lógica de apresentação
    public string GerarRelatorio()
    {
        return $"Usuário: {Nome} | Email: {Email}";
    }
}

Por que isso é um problema?

Se o time de infraestrutura migrar o banco para PostgreSQL, você abre Usuario para mexer em Salvar. Se o time de marketing mudar o template do e-mail, você abre a mesma classe para mexer em EnviarBoasVindas. São mudanças completamente independentes — mas estão todas no mesmo lugar.


A solução: separar por responsabilidade

// cada classe tem um único motivo para mudar

// responsabilidade 1: representar e validar os dados do usuário
public class Usuario
{
    public string Nome { get; }
    public string Email { get; }

    public Usuario(string nome, string email)
    {
        if (string.IsNullOrWhiteSpace(nome) || nome.Length < 2)
            throw new ArgumentException("Nome inválido");

        if (!email.Contains("@"))
            throw new ArgumentException("Email inválido");

        Nome = nome;
        Email = email;
    }
}


// responsabilidade 2: persistência — muda só se o banco mudar
public class UsuarioRepository
{
    private readonly SqlConnection _connection;

    public UsuarioRepository(SqlConnection connection)
    {
        _connection = connection;
    }

    public int Salvar(Usuario usuario)
    {
        var cmd = new SqlCommand(
            "INSERT INTO Usuarios (Nome, Email) VALUES (@Nome, @Email); SELECT SCOPE_IDENTITY();",
            _connection
        );
        cmd.Parameters.AddWithValue("@Nome", usuario.Nome);
        cmd.Parameters.AddWithValue("@Email", usuario.Email);
        return Convert.ToInt32(cmd.ExecuteScalar());
    }

    public Usuario? BuscarPorEmail(string email)
    {
        var cmd = new SqlCommand(
            "SELECT Nome, Email FROM Usuarios WHERE Email = @Email",
            _connection
        );
        cmd.Parameters.AddWithValue("@Email", email);

        using var reader = cmd.ExecuteReader();
        if (!reader.Read()) return null;

        return new Usuario(reader.GetString(0), reader.GetString(1));
    }
}


// responsabilidade 3: notificação — muda só se a lógica de e-mail mudar
public class ServicoNotificacao
{
    private readonly string _smtpHost;
    private readonly int _smtpPort;

    public ServicoNotificacao(string smtpHost, int smtpPort)
    {
        _smtpHost = smtpHost;
        _smtpPort = smtpPort;
    }

    public void EnviarBoasVindas(Usuario usuario)
    {
        var client = new SmtpClient(_smtpHost, _smtpPort);
        var mensagem = new MailMessage(
            "[email protected]",
            usuario.Email,
            "Bem-vindo!",
            $"Olá, {usuario.Nome}! Seja bem-vindo."
        );
        client.Send(mensagem);
    }
}


// quem orquestra tudo: o use case / application service
public class CadastrarUsuarioUseCase
{
    private readonly UsuarioRepository _repository;
    private readonly ServicoNotificacao _notificacao;

    public CadastrarUsuarioUseCase(
        UsuarioRepository repository,
        ServicoNotificacao notificacao)
    {
        _repository = repository;
        _notificacao = notificacao;
    }

    public Usuario Executar(string nome, string email)
    {
        var usuario = new Usuario(nome, email);
        _repository.Salvar(usuario);
        _notificacao.EnviarBoasVindas(usuario);
        return usuario;
    }
}

Agora cada classe tem um único dono. Se o banco mudar, só UsuarioRepository é alterado. Se o e-mail mudar, só ServicoNotificacao. A classe Usuario só muda se as regras de negócio do usuário mudarem.


Como identificar violações do SRP no seu código

"Quem pediria para mudar isso?" Se a resposta for mais de um time ou papel diferente, provavelmente está violando o SRP.

"Quando eu testo essa classe, quantas coisas precisam ser mockadas?" Se um teste unitário de Usuario precisa mockar banco, SMTP e serviço de log ao mesmo tempo, a classe está fazendo coisas demais.

"Se eu mudar o banco de dados, quantos arquivos não relacionados ao banco vou precisar tocar?" A resposta deveria ser: apenas os arquivos de repositório/persistência.


Resumo

Antes

Depois

Uma classe faz validação, persistência e envio de e-mail

Cada responsabilidade em sua própria classe

Mudança no banco afeta código de e-mail

Mudança no banco afeta apenas o repositório

Testes precisam mockar tudo

Testes unitários isolados e simples

Difícil de reutilizar partes isoladas

ServicoNotificacao pode ser usada em outros fluxos

O SRP é a base de tudo que vem depois no SOLID. Com ele, o código começa a se organizar naturalmente.


No próximo post da série: O — Open/Closed Principle. Como escrever código que aceita novas funcionalidades sem precisar ser modificado.

Apoie o conteúdo

Gostou da postagem? Me pague um café.

Se este conteúdo te ajudou, você pode apoiar com qualquer valor via PayPal ou Pix.

Apoiar via PayPal

Posts relacionados

Continue lendo a partir de temas conectados a este post.

Comentários

Comentários passam por aprovação antes de ficarem visíveis.

Faça login para comentar neste post.

Kamilla

02/05/2026

Muito bom

Tawan

30/04/2026

Teste