O documento resume os cinco princípios SOLID da programação orientada a objetos. São eles: SRP (Princípio da Responsabilidade Única), OCP (Princípio Aberto-Fechado), LSP (Princípio da Substituição de Liskov), ISP (Princípio da Segregação de Interfaces) e DIP (Princípio da Inversão de Dependência). Exemplos de cada um dos princípios são fornecidos para ilustrar como aplicá-los corretamente na programação orientada a objetos.
1. Os cinco princípios ágeis de POO
Autor: Ramon Valerio da Silva
Desenvolvedor de Sistemas na Tecgraf PUC-RIO
E-mail: ramonvalerios@gmail.com
2. Os Princípios SOLID
Identificados por Robert C. Martin (mais conhecido como Uncle Bob) por volta do ano 2000.
● SRP - Princípio da Responsabilidade Única
● OCP - Princípio do Aberto e Fechado
● LSP - Princípio da Substituição de Liskov
● ISP - Princípio da Segregação de Interfaces
● DIP - Princípio da Inversão de Dependência
3. SOLID - Benefícios
● Fácil de se manter, adaptar e se ajustar às alterações de
escopo;
● Fácil de entender e testar;
● Construído para ter alterações com o menor esforço
possível;
● Possibilidade de ser reaproveitado;
● Maior tempo possível em produção;
4. SOLID - Possíveis problemas evitados
● Dificuldade na testabilidade / Criação de testes de
unidade;
● Código espaguete, sem estrutura ou padrão;
● Dificuldades de isolar funcionalidades;
● Duplicação de código, uma alteração precisa ser feita
em N pontos;
● Fragilidade, o código quebra facilmente em vários
pontos após alguma mudança.
5. SOLID - SRP(Single Responsability Principle)
“O SRP é o primeiro e mais importante, e se não respeitá-lo,
provavelmente vai comprometer o resto dos princípios.”
“Uma classe deve ter um, e apenas um, motivo para ser
modificada”.
● Um arquivo de classe, deve ter apenas uma classe.
● Um método deve ter apenas uma responsabilidade.
● Um evento deve ter apenas uma responsabilidade.
6. SRP - Estrutura da violação
“Esta classe está assumindo muitas responsabilidades e violando os princípios do SRP.”
7. SRP - Quantas violações esta classe possui?
public class Cliente
{
public int Id { get; set; }
public string Nome { get; set; }
public string Email { get; set; }
public string CPF { get; set; }
public DateTime DataCadastro { get;set;}
public string Incluir()
{
if (!Email.Contains("@"))
return "Cliente com e-mail inválido";
if (CPF.Length != 11)
return "Cliente com CPF inválido";
using (var conexao = new OracleConnection())
{
conexao.ConnectionString = "MinhaConnectionString";
var cmd = new OracleCommand(conexao);
cmd.CommandType = CommandType.Text;
cmd.CommandText = "INSERT INTO CLIENTE (NOME,
EMAIL CPF, DATACADASTRO) VALUES (@nome, @email,
@cpf, @dataCad))";
cmd.Parameters.AddWithValue("nome", Nome);
cmd.Parameters.AddWithValue("email", Email);
cmd.Parameters.AddWithValue("cpf", CPF);
cmd.Parameters.AddWithValue("dataCad", DataCadastro);
conexao.Open();
cmd.ExecuteNonQuery();
conexao.Close();
}
var mail = new MailMessage("tecgraf@tecgraf.com",
Email);
var client = new SmtpClient
{
Port = 25,
DeliveryMethod = SmtpDeliveryMethod.Network,
UseDefaultCredentials = false,
Host = "smtp.google.com"
};
mail.Subject = "Bem-vindo.";
mail.Body = "Parabéns! Você está casastrado.";
client.Send(mail);
return "Cliente cadastrado com sucesso!";
}
8. SRP - Violações
public class Cliente
{
public int Id { get; set; }
public string Nome { get; set; }
public string Email { get; set; }
public string CPF { get; set; }
public DateTime DataCadastro { get;set;}
public string Incluir()
{
if (!Email.Contains("@"))
return "Cliente com e-mail inválido";
if (CPF.Length != 11)
return "Cliente com CPF inválido";
using (var conexao = new OracleConnection())
{
conexao.ConnectionString = "MinhaConnectionString";
var cmd = new OracleCommand(conexao);
cmd.CommandType = CommandType.Text;
cmd.CommandText = "INSERT INTO CLIENTE (NOME,
EMAIL CPF, DATACADASTRO) VALUES (@nome, @email,
@cpf, @dataCad))";
cmd.Parameters.AddWithValue("nome", Nome);
cmd.Parameters.AddWithValue("email", Email);
cmd.Parameters.AddWithValue("cpf", CPF);
cmd.Parameters.AddWithValue("dataCad", DataCadastro);
conexao.Open();
cmd.ExecuteNonQuery();
conexao.Close();
}
var mail = new MailMessage("tecgraf@tecgraf.com",
Email);
var client = new SmtpClient
{
Port = 25,
DeliveryMethod = SmtpDeliveryMethod.Network,
UseDefaultCredentials = false,
Host = "smtp.google.com"
};
mail.Subject = "Bem-vindo.";
mail.Body = "Parabéns! Você está casastrado.";
client.Send(mail);
return "Cliente cadastrado com sucesso!";
}
9. SRP - Violações
public class Cliente
{
public int Id { get; set; }
public string Nome { get; set; }
public string Email { get; set; }
public string CPF { get; set; }
public DateTime DataCadastro { get;set;}
public string Incluir()
{
if (!Email.Contains("@"))
return "Cliente com e-mail inválido";
if (CPF.Length != 11)
return "Cliente com CPF inválido";
using (var conexao = new OracleConnection())
{
conexao.ConnectionString = "MinhaConnectionString";
var cmd = new OracleCommand(conexao);
cmd.CommandType = CommandType.Text;
cmd.CommandText = "INSERT INTO CLIENTE (NOME,
EMAIL CPF, DATACADASTRO) VALUES (@nome, @email,
@cpf, @dataCad))";
cmd.Parameters.AddWithValue("nome", Nome);
cmd.Parameters.AddWithValue("email", Email);
cmd.Parameters.AddWithValue("cpf", CPF);
cmd.Parameters.AddWithValue("dataCad", DataCadastro);
conexao.Open();
cmd.ExecuteNonQuery();
conexao.Close();
}
var mail = new MailMessage("tecgraf@tecgraf.com",
Email);
var client = new SmtpClient
{
Port = 25,
DeliveryMethod = SmtpDeliveryMethod.Network,
UseDefaultCredentials = false,
Host = "smtp.google.com"
};
mail.Subject = "Bem-vindo.";
mail.Body = "Parabéns! Você está casastrado.";
client.Send(mail);
return "Cliente cadastrado com sucesso!";
}
10. SRP - Violações
public class Cliente
{
public int Id { get; set; }
public string Nome { get; set; }
public string Email { get; set; }
public string CPF { get; set; }
public DateTime DataCadastro { get;set;}
public string Incluir()
{
if (!Email.Contains("@"))
return "Cliente com e-mail inválido";
if (CPF.Length != 11)
return "Cliente com CPF inválido";
using (var conexao = new OracleConnection())
{
conexao.ConnectionString = "MinhaConnectionString";
var cmd = new OracleCommand(conexao);
cmd.CommandType = CommandType.Text;
cmd.CommandText = "INSERT INTO CLIENTE (NOME,
EMAIL CPF, DATACADASTRO) VALUES (@nome, @email,
@cpf, @dataCad))";
cmd.Parameters.AddWithValue("nome", Nome);
cmd.Parameters.AddWithValue("email", Email);
cmd.Parameters.AddWithValue("cpf", CPF);
cmd.Parameters.AddWithValue("dataCad", DataCadastro);
conexao.Open();
cmd.ExecuteNonQuery();
conexao.Close();
}
var mail = new MailMessage("tecgraf@tecgraf.com",
Email);
var client = new SmtpClient
{
Port = 25,
DeliveryMethod = SmtpDeliveryMethod.Network,
UseDefaultCredentials = false,
Host = "smtp.google.com"
};
mail.Subject = "Bem-vindo.";
mail.Body = "Parabéns! Você está casastrado.";
client.Send(mail);
return "Cliente cadastrado com sucesso!";
}
11. SRP - Violações
public class Cliente
{
public int Id { get; set; }
public string Nome { get; set; }
public string Email { get; set; }
public string CPF { get; set; }
public DateTime DataCadastro { get;set;}
public string Incluir()
{
if (!Email.Contains("@"))
return "Cliente com e-mail inválido";
if (CPF.Length != 11)
return "Cliente com CPF inválido";
using (var conexao = new OracleConnection())
{
conexao.ConnectionString = "MinhaConnectionString";
var cmd = new OracleCommand(conexao);
cmd.CommandType = CommandType.Text;
cmd.CommandText = "INSERT INTO CLIENTE (NOME,
EMAIL CPF, DATACADASTRO) VALUES (@nome, @email,
@cpf, @dataCad))";
cmd.Parameters.AddWithValue("nome", Nome);
cmd.Parameters.AddWithValue("email", Email);
cmd.Parameters.AddWithValue("cpf", CPF);
cmd.Parameters.AddWithValue("dataCad", DataCadastro);
conexao.Open();
cmd.ExecuteNonQuery();
conexao.Close();
}
var mail = new MailMessage("tecgraf@tecgraf.com",
Email);
var client = new SmtpClient
{
Port = 25,
DeliveryMethod = SmtpDeliveryMethod.Network,
UseDefaultCredentials = false,
Host = "smtp.google.com"
};
mail.Subject = "Bem-vindo.";
mail.Body = "Parabéns! Você está casastrado.";
client.Send(mail);
return "Cliente cadastrado com sucesso!";
}
12. SRP - Estrutura da solução
“Estas classes possuem apenas um motivo para serem alteradas, assumindo uma
única responsabilidade, e respeitando o SRP.”
13. SRP - Soluções
public class Cliente {
public int Id { get; set; }
public string Nome { get; set; }
public string Email { get; set; }
public string CPF { get; set; }
public DateTime DataCadastro { get; set; }
public bool IsValid() {
return EmailServices.IsValid(Email) && CPFServices.IsValid(CPF);
}
}
14. SRP - Soluções
public class ClienteRepository {
public void Incluir(Cliente cliente) {
using (var conexao = new OracleConnection()) {
conexao.ConnectionString = "MinhaConnectionString";
var cmd = new OracleCommand(conexao);
cmd.CommandText = "INSERT INTO CLIENTE (NOME, EMAIL CPF, DATACADASTRO) VALUES (@nome, @email, @cpf, @dataCad))";
cmd.Parameters.AddWithValue("nome", cliente.Nome);
cmd.Parameters.AddWithValue("email", cliente.Email);
cmd.Parameters.AddWithValue("cpf", cliente.CPF);
cmd.Parameters.AddWithValue("dataCad", cliente.DataCadastro);
conexao.Open();
cmd.ExecuteNonQuery();
conexao.Close();
}
15. SRP - Soluções
public class ClienteService {
public string Incluir(Cliente cliente)
{
if (!cliente.IsValid())
return "Dados inválidos";
var repositorio = new ClienteRepository();
repositorio.Incluir(cliente);
EmailServices.Enviar("tecgraf@tecgraf.com", cliente.Email, "Bem-vindo", "Parabéns está Cadastrado");
return "Cliente cadastrado com sucesso";
}
}
16. SRP - Soluções
public static class EmailServices {
public static bool IsValid(string email) {
return email.Contains("@");
}
public static void Enviar(string de, string para, string assunto, string mensagem) {
var mail = new MailMessage(de, para);
var client = new SmtpClient { Port = 25, DeliveryMethod = SmtpDeliveryMethod.Network,
UseDefaultCredentials = false, Host = "smtp.google.com"
};
mail.Subject = assunto;
mail.Body = mensagem;
client.Send(mail);
}
}
17. SRP - Soluções
public static class CPFServices
{
public static bool IsValid(string cpf)
{
return cpf.Length == 11;
}
}
18. SOLID - OCP(Open Closed Principle)
“Você deve ser capaz de estender um
comportamento de uma classe, sem modificá-la.”
“Entidades de softwares (classes, módulos, funções, etc) devem
estar abertas para extensão, mas fechadas para modificação.”
19. OCP - Estrutura da violação
“A classe DebitoConta está assumindo muitas responsabilidades e
violando os princípios do SRP.”
20. OCP - Onde estão as violações desta classe?
public class DebitoConta {
public void Debitar(decimal valor, string conta, TipoConta tipoConta) {
if (tipoConta == TipoConta.Corrente) {
// Debita Conta Corrente
}
if (tipoConta == TipoConta.Poupanca) {
// Valida Aniversário da Conta
// Debita Conta Poupança
}
}
}
public enum TipoConta
{
Corrente,
Poupanca
}
21. OCP - Violações
public class DebitoConta {
public void Debitar(decimal valor, string conta, TipoConta tipoConta) {
if (tipoConta == TipoConta.Corrente) {
// Debita Conta Corrente
}
if (tipoConta == TipoConta.Poupanca) {
// Valida Aniversário da Conta
// Debita Conta Poupança
}
}
}
public enum TipoConta
{
Corrente,
Poupanca
}
22. OCP - Estrutura da solução
“Estas classes estão assumindo uma única responsabilidade.”
23. OCP - Soluções
public abstract class DebitoConta
{
public string NumeroTransacao { get; set; }
public abstract string Debitar(decimal valor, string conta);
}
24. OCP - Soluções
public class DebitoContaCorrente : DebitoConta
{
public override string Debitar(decimal valor, string conta)
{
// Debita Conta Corrente
return NumeroTransacao;
}
}
25. OCP - Soluções
public class DebitoContaPoupanca : DebitoConta
{
public override string Debitar(decimal valor, string conta)
{
// Valida Aniversário da Conta
// Debita Conta Poupança
return NumeroTransacao;
}
}
26. OCP - Soluções
public class DebitoContaInvestimento : DebitoConta
{
public override string Debitar(decimal valor, string conta)
{
// Debita Conta Investimento
// Isentar Taxas
return NumeroTransacao;
}
}
27. SOLID - LSP(Liskov Substitution Principle)
“Uma classe base deve poder ser substituída pela sua classe derivada.”
“Se nada como um pato, voa como um pato, porém precisa de baterias, provavelmente você
possui um problema de abstração.”
“As classes derivadas devem ser substituíveis por suas classes base.”
36. LSP - Violações
public class Quadrado : Retangulo
{
public override double Altura
{
set { base.Altura = base.Largura = value; }
}
public override double Largura
{
set { base.Altura = base.Largura = value; }
}
}
37. LSP - Violações
public class Program {
private static void ObterAreaRetangulo(Retangulo retangulo) {
Console.WriteLine("Calculo da área do Retangulo");
Console.WriteLine(retangulo.Altura + " x " + retangulo.Largura);
Console.WriteLine(“Resultado: ” + retangulo.GetArea());
Console.ReadKey();
}
private static void Main() {
var quadrado = new Quadrado(){ Altura = 10, Largura = 5};
ObterAreaRetangulo(quadrado);
}
}
Calculo da área do Retangulo
5 x 5
Resultado: 25
38. SOLID - ISP(Interface Segregation Principle)
“Classes não devem ser forçadas a depender de
métodos que não usam.”
“Muitas interfaces específicas são melhores do que
uma única interface.”
40. ISP - Violações
public class FormCadastroCliente : IFormCadastro
{
public void Validar()
{
// Validar CPF, Email
}
public void Salvar()
{
// Insert na tabela Cliente
}
public void EnviarEmail()
{
// Enviar e-mail para o cliente
}
}
41. ISP - Violações
public class FormCadastroProduto : IFormCadastro
{
public void Validar()
{
// Validar valor
}
public void Salvar()
{
// Insert tabela Produto
}
public void EnviarEmail()
{
// Produto não tem e-mail, o que eu faço agora???
}
}
43. ISP - Soluções
public class CadastroCliente : ICadastroCliente {
public void Validar() {
// Validar CPF, Email
}
public void Salvar() {
// Insert na tabela Cliente
}
public void EnviarEmail() {
// Enviar e-mail para o cliente
}
}
public class CadastroProduto : ICadastroProduto {
public void Validar() {
// Validar valor
}
public void Salvar() {
// Insert tabela Produto
}
}
44. SOLID - DIP(Dependency Inversion Principle)
“Módulos de alto nível não devem depender de
módulos de baixo nível. Ambos devem depender
de abstrações;”
“Abstrações não devem depender de detalhes;
Detalhes devem depender de abstrações.”
45. DIP - Estrutura da violação
“Estas classes não contém interfaces para padronizar seus métodos e também não possui o recurso da abstração
das interfaces, deixando a estrutura fortemente acoplada, ou seja, com muitas dependências, violando os princípios
do DIP.”
46. DIP - Violações
public class Cliente {
public int Id { get; set; }
public string Nome { get; set; }
public string Email { get; set; }
public string CPF { get; set; }
public DateTime DataCadastro { get; set; }
public bool IsValid() {
return EmailServices.IsValid(Email) && CPFServices.IsValid(CPF);
}
}
“FORTE ACOPLAMENTO!”
47. DIP - Violações
public class ClienteService
{
public string Incluir(Cliente cliente)
{
if (!cliente.IsValid())
return "Dados inválidos";
var repositorio = new ClienteRepository();
repositorio.Incluir(cliente);
EmailServices.Enviar("tecgraf@tecgraf.com", cliente.Email, "Bem-vindo", "Parabéns está Cadastrado");
return "Cliente cadastrado com sucesso";
}
}
“FORTE ACOPLAMENTO!”
48. DIP - Violações
public static class CPFServices
{
public static bool IsValid(string cpf)
{
return cpf.Length == 11;
}
}
49. DIP - Violações
public static class EmailServices {
public static bool IsValid(string email) {
return email.Contains("@");
}
public static void Enviar(string de, string para, string assunto, string mensagem) {
var mail = new MailMessage(de, para);
var client = new SmtpClient {
Port = 25,
DeliveryMethod = SmtpDeliveryMethod.Network,
UseDefaultCredentials = false,
Host = "smtp.google.com"
};
mail.Subject = assunto;
mail.Body = mensagem;
client.Send(mail);
}
}
50. DIP - Violações
public class ClienteRepository {
public void Incluir(Cliente cliente) {
using (var conexao = new OracleConnection())
{
conexao.ConnectionString = "MinhaConnectionString";
var cmd = new OracleCommand(conexao);
cmd.CommandText = "INSERT INTO CLIENTE(NOME, EMAIL CPF, DATACADASTRO) VALUES(@nome, @email, @cpf, @dataCad)";
cmd.Parameters.AddWithValue("nome", cliente.Nome);
cmd.Parameters.AddWithValue("email", cliente.Email);
cmd.Parameters.AddWithValue("cpf", cliente.CPF);
cmd.Parameters.AddWithValue("dataCad", cliente.DataCadastro);
conexao.Open();
cmd.ExecuteNonQuery();
conexao.Close();
}
}
}
51. DIP - Estrutura da solução
IClienteRepository
IClienteService
IEmailService
ICPFService
“Agora eu posso usar as interfaces como abstrações das classes que o implementam, deixando a
estrutura com baixo acoplamento, ou seja sem dependência.”
52. DIP - Soluções
public class Cliente {
private readonly ICPFServices _cpfServices;
private readonly IEmailServices _emailServices;
public Cliente(ICPFServices cpfServices, IEmailServices emailServices) {
_cpfServices = cpfServices; _emailServices = emailServices;
}
public int ClienteId { get; set; }
public string Nome { get; set; }
public string Email { get; set; }
public string CPF { get; set; }
public DateTime DataCadastro { get; set; }
public bool IsValid(){
return _emailServices.IsValid(Email) && _cpfServices.IsValid(CPF);
} }
53. DIP - Soluções
public class ClienteService : IClienteServices {
private readonly IClienteRepository _clienteRepository;
private readonly IEmailServices _emailServices;
public ClienteServices(IClienteRepository clienteRepository, IEmailServices emailServices) {
_clienteRepository = clienteRepository; _emailServices = emailServices;
}
public string Incluir(Cliente cliente) {
if (!cliente.IsValid())
return "Dados inválidos";
_clienteRepository.Incluir(cliente);
_emailServices.Enviar("tecgraf@tecgraf.com", cliente.Email, "Bem-vindo", "Parabéns está Cadastrado");
return "Cliente cadastrado com sucesso";
}
}
54. DIP - Exemplo de um Container de DI
private static void RegisterServices(IKernel kernel)
{
//Service
kernel.Bind<IClienteService>().To<ClienteService>();
kernel.Bind<ICPFService>().To<CPFService>();
kernel.Bind<IEmailService>().To<EmailService>();
//Repository
kernel.Bind<IClienteRepository>().To<ClienteRepository>();
}
55. “Um homem sábio pode aprender mais com uma pergunta tola do que um
tolo pode aprender a partir de uma resposta sensata.”
1 - Quais os benefícios em usar o SRP?
2 - Por que usar OCP se eu posso colocar mais um if ou criar enums e
flags que resolve muito mais rápido?
3 - O que viola o LSP?
4 - No ISP usar muitas interfaces específicas é bom ou ruim, e
porque?
5 - Quais são as vantagens do DIP? Quais princípios eu devo
respeitar para aplicar o DIP?
6 - Quais são os benefícios de um software usando princípios SOLID?