Em busca de uma arquitetura sólida – Parte 1
Ultimamente venho lendo bastante sobre boas práticas de programação, em especial as promovidas pela comunidade ALT.NET.
Da sopa de letras que sempre nos vemos quando começamos a fazer esse tipo de pesquisa (DDD, TDD, BDD, DI, IoC, etc etc) o que parece trazer maior consenso são os princípios defendidos por Robert C. Martin para o desenvolvimento de código orientado a objetos, detalhados em seu artigo “The Principles of OOD”, que buscam reduzir o acoplamento, facilitar o gerenciamento de dependências, e aumentar a coesão, mantenabilidade e testabilidade de código, e que atendem pela sigla S.O.L.I.D.:
- Single Responsibility Principle, ou Princípio de Responsabilidade Única;
- Open-Closed Principle, ou Princípio Aberto-Fechado;
- Liskov Subtituition Principle, ou Princípio de Substituição de Liskov;
- Interface Segregation Principle, ou Princípio de Segregação de Interface;
- Dependency Inversion Principle, ou Princípio da Inversão de Controle.
Nesse e nos próximos posts, irei explicar cada um desses princípios, procurando mostrar quais vantagens eles trazem para nós programadores OO. Vamos então começar com a letra S
O Princípio da Responsabilidade Única (SRP)
Robert Martin define assim esse princípio:
Uma classe deve ter apenas um motivo para mudança.
Uma outra definição popular desse princípio é:
Uma classe deve ter uma e apenas uma responsabilidade.
Se a ligação entre essas duas definições não é muito clara, basta pensar o seguinte: quanto mais responsabilidades uma classe possui, quanto mais tarefas uma classe executa, maior o número de pontos de mudança na mesma.
Muitas vezes não é possível seguir tal princípio ao pé da letra, por questões práticas, mas ao buscarmos seguir tal ideal teremos classes mais coesas, fáceis de entender, alterar e testar.
Vamos olhar um exemplo prático, uma classe típica que todos nós já desenvolvemos aos montes: nessa classe, uma lista de preços é lido de um banco de dados, um determinado índice é buscado em um serviço web, esse índice é então aplicado em todos os preços e finalmente os novos preços são salvos no banco de dados.
namespace SOLID.SRP
{
public class Indexador
{
public void AtualizaPrecos()
{
var precosAtuais = CarregaPrecosAtuais();
var indiceAtual = BuscaIndiceCorrente();
//aplica o indice atual em todos os valores
var precosAtualizados = ....
SalvaPrecosAtualizados(precosAtualizados);
}
private IEnumerable<double> CarregaPrecosAtuais()
{
//busca os valores no banco de dados da empresa
}
private double BuscaIndiceCorrente()
{
//busca o indice atual, em um servico na web
}
private void SalvaPrecosAtualizados(IEnumerable<double> valoresAtualizados)
{
//grava os valores atualizados no banco de dados da empresa
}
}
}
A gente sabe que esse tipo de classe começa simples, mas logo complica: um dia alguém descobre que, caso o serviço que fornece os índices esteja fora do ar, a classe deverá ler o índice de um arquivo texto; depois um bug é arquivado, informando que o índice não deve ser aplicado em fins-de-semana e feriados, e para tanto vamos precisar integrar algum tipo de calendário de feriados na classe. Rapidamente, nos vemos com uma classe com 900 linhas de código, e que ninguém entende direito!
Para aplicar o SRP, basta checar quantos motivos diferentes conseguimos ver para que a classe tenha que mudar:
- Mudança na estrutura ou tipo de banco de dados;
- Mudança no serviço de índices;
- Mudança em como o índice é aplicado aos preços
Vamos tentar então isolar cada um desses elementos. Como o índice pode agora vir tanto de um serviço web quanto um arquivo text, acho que faz sentido criar uma interface com um método BuscaIndiceCorrente, de forma que podemos implementar uma classe que busque tal índice da web, e outra que busque tal índice de um arquivo texto. Como a classe Indexador vai referenciar a interface, e não uma das classes concretas, fica fácil trocar a estratégia em tempo de execução, bem como criar uma classe “fake” em nossos testes (unit tests) que retorne um valor qualquer. Matamos 2 coelhos com uma pedrada!
O mesmo conceito será seguido para o repositório de preços.
Aqui está a declaração da interface:
namespace SOLID.SRP
{
public interface IServicoIndice
{
double BuscaIndiceCorrente();
}
}
Aqui então está a nossa classe principal. Note que as suas dependências (o serviço de índices e o repositório de preços) são passados à classe no seu construtor. É como se disséssemos à classe “faça o que você sabe fazer, e aqui estão as ferramentas que você vai precisar”. Como disse anteriormente, note que fica fácil então trocarmos essas ferramentas quando necessário, mesmo em tempo de execução…uma arquitetura bem elegante, e que retornaremos no futuro
namespace SOLID.SRP
{
public class Indexador
{
private IServicoIndice _servicoIndice;
private IRepositorioPrecos _repositorio;
public Indexador(IServicoIndice servicoIndice, IRepositorioPrecos repositorio)
{
_servicoIndice = servicoIndice;
_repositorio = repositorio;
}
public void AtualizaPrecos()
{
var precosAtuais = _repositorio.CarregaPrecosAtuais();
var indiceAtual = _servicoIndice.BuscaIndiceCorrente();
//aplica o indice atual em todos os valores
var valoresAtualizados = ..... ;
_repositorio.SalvaPrecosAtualizados(valoresAtualizados);
}
}
}
Em nosso novo código, vemos que agora cada classe é responsável por apenas um tipo de tarefa, e tem apenas um motivo para mudar. Sucesso!
Daqui a uns dias postarei um novo artigo cobrindo o “O” de SOLID. Abraços!