
Veja o índice completo do tópico “S.O.L.I.D”
Olá pessoas …
Continuando a nossa saga de artigos, vamos agora ver a letra O do acróstico, que define a sigla OCP Open/Closed Principle (Princípio aberto/ fechado).
Definindo OCP

Momento piadinha infame do artigo
Não iremos falar sobre o Robocop, mas toda vez que eu falo a sigla OCP (Omni Consumer Products) eu vejo o Robocop na minha mente
Bom, vamos ao que interessa…
 |
Você deve ser capaz de estender um comportamento de uma classe sem modificá-lo. |
Esta declaração quer dizer que você tem que escrever suas classes de forma que elas possam ser herdadas e ter seu comportamento estendido ao invés de alterar o código existente. Para fazer isso, iremos usar o que chamamos de herança e abstração.
 |
Para saber mais.
Caso não esteja familiarizado com os termos herança e abstração, dê uma lida no artigo sobre “Herança” e no artigo sobre “Abstração“.
Tenho certeza que suas dúvidas serão sanadas. |
Problemas em ferir o princípio OCP
- Reaproveitamento de código é praticamente nulo. CTRL+C/ CTRL+V, não é reaproveitamento de código;
- As classes podem fazer partes de componentes espalhados pela aplicação;
- O que acontece se precisarmos de uma nova implementação?
- Colocamos mais um if/else if, um switch…case ?
- Criamos uma nova classe e copiamos o código?
- Recompilar e fazer deploy em todos os componentes impactados;
Espero que você tenha um bom caso de testes escrito para cada situação.
 |
Certo ou errado?
Abaixo vou colocar os códigos “ERRADO” e “CERTO” e ao final, analisar cada um. |
Ferindo o OCP
 |
Isto está errado.
Primeiro, vamos criar nossa classe de venda para atender as vendas da nossa aplicação.
/*
* Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
*/
using System;
using System.Collections.Generic;
namespace Solid.Errado
{
public class Venda
{
#region Public Properties
public int Codigo { get; set; }
public int CodigoCliente { get; set; }
public List<string> Itens { get; set; }
public double TotalVenda { get; set; }
#endregion Public Properties
#region Public Methods
public void Salvar()
{
Validar();
Console.WriteLine("A venda foi salva.");
CalcularTotal();
AtualizarSaldo();
}
private void CalcularTotal()
{
Console.WriteLine("O total da venda foi calculado.");
}
public void Validar()
{
if(CodigoCliente == 0)
throw new Exception("O código do cliente é obrigatório.");
if((Itens?.Count ?? 0) == 0)
throw new Exception("A quantidade de itens vendida não pode ser zero.");
}
public void AtualizarSaldo()
{
Utility.AtualizarSaldo.AtualizarSaldos(new AtualizarSaldoAnual(),
new AtualizarSaldoMensal(),
new AtualizarSaldoDiario());
}
#endregion Public Methods
}
}
Com a classe de vendas criada, precisamos atualizar os saldos de estoque, pois a venda faz uma saída em nossos produtos.
Esta classe é um utilitário de atualização de saldos de estoque, e recebe objetos para realizar os cálculos de saldo de estoque.
/*
* Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
*/
namespace Solid.Errado.Utility
{
public static class AtualizarSaldo
{
#region Public Methods
public static void AtualizarSaldos(params object[] saldos)
{
foreach(var saldo in saldos)
{
if(saldo.GetType() == typeof(AtualizarSaldoAnual))
((AtualizarSaldoAnual)saldo).Atualizar();
else if(saldo.GetType() == typeof(AtualizarSaldoMensal))
((AtualizarSaldoMensal)saldo).Atualizar();
else if(saldo.GetType() == typeof(AtualizarSaldoDiario))
((AtualizarSaldoDiario)saldo).Atualizar();
}
}
#endregion Public Methods
}
}
E abaixo, as classes de atualização de saldo diário, mensal e anual.
Saldo Diário
/*
* Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
*/
using System;
namespace Solid.Errado
{
public class AtualizarSaldoDiario
{
#region Public Methods
public void Atualizar()
{
Console.WriteLine("O saldo DIARIO foi atualizado.");
}
#endregion Public Methods
}
}
Saldo Mensal
/*
* Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
*/
using System;
namespace Solid.Errado
{
public class AtualizarSaldoMensal
{
#region Public Methods
public void Atualizar()
{
Console.WriteLine("O saldo MENSAL foi atualizado.");
}
#endregion Public Methods
}
}
Saldo Anual
/*
* Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
*/
using System;
namespace Solid.Errado
{
public class AtualizarSaldoAnual
{
#region Public Methods
public void Atualizar()
{
Console.WriteLine("O saldo ANUAL foi atualizado.");
}
#endregion Public Methods
}
}
|
Logo de cara já percebemos que a classe de vendas fere completamente o Princípio da Responsabilidade Única, ela se “valida”, ele atualiza os saldos de estoque entre outros problemas que você já é capaz de identificar com a leitura do primeiro artigo.
Mas o que realmente nos interessa aqui, são as classes de saldos. Estas ferem completamente o princípio OCP (Aberto/ Fechado).
- Mesmo que eu faça a herança destas classes, por exemplo, para criar um saldo quinzenal, eu não consigo substituir o comportamento do método atualizar;
- O utilitário de atualização de saldos, classe “Utility.AtualizarSaldo“, tem que conhecer as classes de saldos e fazer um “if” para identificar qual é o tipo de saldo que deverá ser atualizado;
- Se eu precisar implementar mais um tipo de saldo, tenho que lembrar deste “if” e criar um método com o mesmo nome “Atualizar” para manter um mínimo de padrão;
- Este utilitário aceita tipos “object“, isso quer dizer que se eu passar uma classe errada para o meu método, posso ter um erro em tempo de execução, ou não ter meus saldos de estoque calculados corretamente;
- Percebemos que não houve reaproveitamento de código entre as classes de saldos;
Corrigindo o princípio
Neste momento, iremos corrigir o princípio e ao final analisar as modificações que foram realizadas para atender ao princípio do OCP.
 |
Isto está correto.
Veja as classes que foram criadas:
Classe de venda para realização das vendas em nosso estabelecimento.
/*
* Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
*/
using Solid.Certo.Persistence.Abstract;
using Solid.Certo.Repository;
using System.Collections.Generic;
namespace Solid.Certo.Persistence
{
public class Venda: EntityBase
{
#region Public Properties
public int CodigoCliente { get; set; }
public List<string> Itens { get; set; }
public double TotalVenda { get; set; }
#endregion Public Properties
#region Public Methods
public override void Salvar()
{
DBContext<Venda>.Salvar(this);
}
#endregion Public Methods
}
}
Abstração para os saldos, desta forma eu posso herdar minha classe principal e modificar apenas o que eu preciso.
/*
* Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
*/
namespace Solid.Certo.Utility.Abstract
{
public abstract class AtualizaSaldoBase
{
#region Public Methods
public abstract void Atualizar();
#endregion Public Methods
}
}
Classe concreta de atualização de saldos diários.
/*
* Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
*/
using Solid.Certo.Utility.Abstract;
using System;
namespace Solid.Certo.Utility
{
public class AtualizaSaldoDiario: AtualizaSaldoBase
{
#region Public Methods
public override void Atualizar()
{
Console.WriteLine("O saldo DIÁRIO foi atualizado.");
}
#endregion Public Methods
}
}
Classe concreta de atualização de saldos mensais.
/*
* Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
*/
using Solid.Certo.Utility.Abstract;
using System;
namespace Solid.Certo.Utility
{
public class AtualizaSaldoMensal: AtualizaSaldoBase
{
#region Public Methods
public override void Atualizar()
{
Console.WriteLine("O saldo MENSAL foi atualizado.");
}
#endregion Public Methods
}
}
Classe concreta de atualização de saldos anuais.
/*
* Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
*/
using Solid.Certo.Utility.Abstract;
using System;
namespace Solid.Certo.Utility
{
public class AtualizaSaldoAnual: AtualizaSaldoBase
{
#region Public Methods
public override void Atualizar()
{
Console.WriteLine("O saldo ANUAL foi atualizado.");
}
#endregion Public Methods
}
}
Estratégia de venda, responsável pela nossa regra de negócio quando a venda for realizada.
/*
* Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
*/
using Solid.Certo.Persistence;
using Solid.Certo.Strategy.Abstract;
using Solid.Certo.Utility.Abstract;
using System;
using System.Collections.Generic;
namespace Solid.Certo.Strategy
{
public class VendaStrategy: PersistenceStrategyBase<Venda>
{
#region Public Methods
public override void AntesDeSalvar(Venda entity)
{
base.AntesDeSalvar(entity);
//vamos simular uma cálculo de total de venda
Entidade.TotalVenda = 30;
Console.WriteLine("O total da venda foi calculado");
}
public override IEnumerable<string> Validar()
{
if(Entidade.CodigoCliente == 0)
yield return "O código do cliente é obrigatório.";
if((Entidade.Itens?.Count ?? 0) == 0)
yield return "A quantidade de itens vendida não pode ser zero.";
yield break;
}
public override void DepoisDeSalvar(Venda entity)
{
base.DepoisDeSalvar(entity);
Service.AtualizaSaldoService.AtualizarSaldos(new List<AtualizaSaldoBase>
{
new Utility.AtualizaSaldoDiario(),
new Utility.AtualizaSaldoMensal(),
new Utility.AtualizaSaldoAnual()
});
}
#endregion Public Methods
}
}
Classe de serviço para atualização de saldos no estoque.
/*
* Classe apenas para fins de exemplo e aprendizado não considera nenhum tipo de validação ou regra ou se utiliza de algum framework.
*/
using Solid.Certo.Utility.Abstract;
using System.Collections.Generic;
namespace Solid.Certo.Service
{
public static class AtualizaSaldoService
{
#region Public Methods
public static void AtualizarSaldos(List<AtualizaSaldoBase> saldos)
{
foreach(AtualizaSaldoBase saldo in saldos)
{
saldo.Atualizar();
}
}
#endregion Public Methods
}
}
|
Como podemos ver, até a classe de vendas foi corrigida. Não fere mais o primeiro princípio.
Como o foco foram as classes de saldos, iremos listar as vantagens desta abordagem.
- Com o uso da abstração; (clique para saber mais sobre Abstração); conseguimos isolar os métodos comuns das nossas classes de cálculo de saldos;
- Fizemos com que as classes filhas, implementassem o método “Atualizar” para que cada uma saiba como fazer a atualização de saldos em estoque;
- As estratégias indicam o momento em que a atualização dos saldos devem ocorrer;
- Se for necessário a inclusão do saldo quinzenal, basta apenas criar a classe e inserir na estratégia o momento em que se deseja atualizar o saldo;
- Não é necessário fazer o uso de “if/else if” ou “switch…case” no momento da atualização de saldos, isso foi resolvido com o Polimorfismo;
- Perceba que ao utilizar a abstração para chamar a atualização de saldos, nos não precisamos mais nos preocupar em fazer “if/else if” e nem se preocupar se o tipo de objeto passado é do tipo saldo. Uma vez que a herança; (clique para saber mais sobre Herança) me permite fazer isto de forma transparente e garante que o tipo passado é do tipo saldo;
- E se necessário fazer uma customização para determinado cliente, podemos usar e abusar do padrão “Strategy“.
O fonte utilizado aqui pode ser baixado pelo GITHub em https://github.com/desenvolvedores-net/ArtigoSOLID
É isso ai pessoal 🙂
Até o próximo
♦ Marcelo