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

