Desenvolvedores.Net - TechBlog

Tag Archives: SRP

LSP Liskov Subtitution Principle (Princípio da Substituição de Liskov)

0
1 Estrela2 Estrelas3 Estrelas4 Estrelas5 Estrelas (Sem votação.)
Loading...
14 de agosto de 2017

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 do acróstico que define a sigla LSP Liskov Substitution Principle (Princípio da Substituição de Liskov).

Definido o LSP

Não tem como falar deste princípio sem saber quem o criou, Barbara Liskov, em 1988.

A definição mais usada, e a mais simples, é:

note-taking Classes derivadas podem ser substituídas por suas classes de base.

Se q(x) é uma propriedade demonstrável dos objetos x de tipo T. Então q(y) deve ser verdadeiro para objetos y de tipo S onde S é um subtipo de T.

Portanto, a visão de “subtipo” defendida por Liskov é baseada na noção da substituição; isto é, se S é um subtipo de T, então os objetos do tipo T, em um programa, podem ser substituídos pelos objetos de tipo S sem que seja necessário alterar as propriedades deste programa.

(fonte: Wikipédia)

 

certo-ou-errado

Certo ou errado?

E lá vamos nós novamente ver como fica o princípio ferido, os problemas que nos trazem e a solução do mesmo …

 Ferindo o princípio

errado

Isto está errado.

Utilitário de atualização de saldos.

/*
 * 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.Errado.LSP.Abstract;

namespace Solid.Errado.LSP
{
    public static class AtualizarSaldo
    {
        #region Public Methods

        public static void AtualizarSaldos(params AtualizaSaldoBase[] saldos)
        {
            foreach(var saldo in saldos)
            {
                if(saldo is AtualizarSaldoAnual)
                    ((AtualizarSaldoAnual)saldo).AtualizarAno();
                else if(saldo is AtualizarSaldoMensal)
                    ((AtualizarSaldoMensal)saldo).AtualizarMes();
                else if(saldo is AtualizarSaldoDiario)
                    ((AtualizarSaldoDiario)saldo).AtualizarDia();
            }
        }

        #endregion Public Methods
    }
}

Abstração para as classes de saldo concretas

/*
 * 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.LSP.Abstract
{
    public abstract class AtualizaSaldoBase
    {
        #region Public Properties

        public double SaldoAtual { get; set; }

        #endregion Public Properties
    }
}

Cálculo de 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 Solid.Errado.LSP.Abstract;
using System;

namespace Solid.Errado.LSP
{
    public class AtualizarSaldoDiario: AtualizaSaldoBase
    {
        #region Public Methods

        public void AtualizarDia()
        {
            Console.WriteLine("O saldo DIARIO foi atualizado.");
        }

        #endregion Public Methods
    }
}

Cálculo de 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 Solid.Errado.LSP.Abstract;
using System;

namespace Solid.Errado.LSP
{
    public class AtualizarSaldoMensal: AtualizaSaldoBase
    {
        #region Public Methods

        public void AtualizarMes()
        {
            Console.WriteLine("O saldo MENSAL foi atualizado.");
        }

        #endregion Public Methods
    }
}

Cálculo de 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 Solid.Errado.LSP.Abstract;
using System;

namespace Solid.Errado.LSP
{
    public class AtualizarSaldoAnual: AtualizaSaldoBase
    {
        #region Public Methods

        public void AtualizarAno()
        {
            Console.WriteLine("O saldo ANUAL foi atualizado.");
        }

        #endregion Public Methods
    }
}

Problemas ao ferir o princípio

  • Primeiro, já matamos o polimorfismo. Na classe de atualização de saldos “public static class AtualizarSaldo” no método “public static void AtualizarSaldos(params AtualizaSaldoBase[] saldos)” temos que fazer “if/else” para determinar que tipo de classe iremos usar para calcular o saldo.
  • Na mesma classe citada acima, mesmo que utilizado a herança nas demais classes de atualização de saldo, eu não consigo o reaproveitamento do código de atualização de saldos, uma vez que eu tenho que identificar qual o tipo de classe e determinar o método que é chamado, veja no fragmento abaixo o “if/else”.
if(saldo is AtualizarSaldoAnual)
    ((AtualizarSaldoAnual)saldo).AtualizarAno();
else if(saldo is AtualizarSaldoMensal)
    ((AtualizarSaldoMensal)saldo).AtualizarMes();
else if(saldo is AtualizarSaldoDiario)
    ((AtualizarSaldoDiario)saldo).AtualizarDia();
  • Violação do princípio OCP
  • Dar manutenção neste código é ter dores de cabeça, uma vez que teremos que lembrar de alterar este método de cálculo de saldos sempre que um novo tipo de saldo for criado.

Corrigindo o princípio

Como o LSP anda de mãos dadas com o OCP, podemos perceber que as classes de saldos definidos no princípio OCP atendem 100% os requisitos deste princípio . Vamos relembrar estas classes:

certo

Isto está correto.

Veja as classes que foram criadas:

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

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

Analisando as classes definidas acima, podemos perceber que:

  • As classes de saldo herdam diretamente da classe “public abstract class AtualizaSaldoBase”;
  • Passamos a ter acesso ao método “Atualizar()”, desta forma eu consigo chamar o método “Atualizar()” a partir da classe mais genérica (Veja: Generalização e Especialização), é utilizado o polimorfismo para evitar o uso de “if/ else”, como podemos ver no fragmento abaixo da classe de serviço de atualização de saldo.
public static class AtualizaSaldoService
{
	#region Public Methods

	public static void AtualizarSaldos(List<AtualizaSaldoBase> saldos)
	{
		foreach(AtualizaSaldoBase saldo in saldos)
		{
			saldo.Atualizar();
		}
	}

	#endregion Public Methods
}
  • Se necessário criar um novo tipo de saldo, basta criar a classe, herdar de “AtualizaSaldoBase” e implementar a chamada onde queremos que o novo saldo seja calculado sem termos que nos preocupar em fazer “if” para identificar o tipo de  saldo que iremos calcular;
  • A classe mais especializada pode ser facilmente convertida na classe mais genérica e atende ao princípio em questão;

Pegadinha e cuidados

Que diabos O patinho feio; história infantil; está fazendo aqui?

Ora! É simples. Se nada como um pato, tem as habilidades de um pato, come igual ao pato mas é um marreco cisne, então você tem um problema de abstração.

Vamos ao exemplo mais clássico de erro de abstração Quadrado x Retângulo. Não poderíamos falar de LSP sem comentar sobre o Quadrado x Retângulo.

Todo quadrado é também um retângulo, pois ambos tem ângulos retos. Mas nem todo retângulo é um quadrado. Quem me disse foi o Professor Procópio do Matemática Rio, no vídeo Qual a Diferença entre Retângulo e Quadrado?

Vamos aos fontes:

Classe de retângulo.

/*
 * 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.QuadradoRetangulo
{
    public class Retangulo
    {
        #region Public Properties

        public virtual int Altura { get; set; }
        public virtual int Largura { get; set; }

        #endregion Public Properties
    }
}

Classe do quadrado que herda de retângulo (Já falei sobre herança?)

/*
 * 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.QuadradoRetangulo
{
    public class Quadrado: Retangulo
    {
    }
}

Utilitário para cálculo de área.

/*
 * 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.Errado.QuadradoRetangulo;

namespace Solid.Errado.Utility
{
    public static class CalculaArea
    {
        #region Public Methods

        //Sim, este método poderia ser da classe retângulo.
        //Mas para efeito de exemplo, vou deixar ele aqui.
        public static int CalcularArea(Retangulo retangulo)
        {
            return retangulo.Altura * retangulo.Largura;
        }

        #endregion Public Methods
    }
}

Chamada de exemplo para ambos os casos

int altura;
int largura;

Console.WriteLine("\r\nInforme a altura:");
altura = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("Informe a largura:");
largura = Convert.ToInt32(Console.ReadLine());

//aqui, podemos criar um quadrado normalmente.
Quadrado quadrado = new Quadrado
{
    Altura = altura,
    Largura = largura
};

//aqui, criamos um retângulo com base em um quadrado, atendendo ao princípio do LSP
Retangulo retanguloQuadrado = new Quadrado
{
    Altura = altura,
    Largura = largura
};

//Aqui criamos um retângulo normalmente.
Retangulo retangulo = new Retangulo
{
    Altura = altura,
    Largura = largura
};

int quadradoAreaEsperada = altura * altura;
int retanguloAreaEsperada = largura * altura;
//o método CalcularArea() espera um retângulo, perceba que podemos passar tanto um quadrado quanto um retângulo. Atendendo ao princípio LSP.
Console.WriteLine($"A área correta do retângulo é {retanguloAreaEsperada} e a calculada foi {Errado.Utility.CalculaArea.CalcularArea(retangulo)}");
Console.WriteLine($"A área correta do quadrado  é {quadradoAreaEsperada} e a calculada foi {Errado.Utility.CalculaArea.CalcularArea(quadrado)}");   

Vamos analisar os fontes e explicar o real problema de não tomarmos cuidado no momento de definirmos nossas abstrações.

As classes definidas, a primeira vista, não ferem o princípio LSP, uma vez que podem ser herdadas, terem seus métodos sobrescritos, são reutilizáveis e eu posso criar a mais genérica pela classe mais especializada, posso utilizar a classe mais genérica como parâmetro e passar a mais especializada, como diz o princípio da substituição.
No caso, como foi mostrado, eu posso criar um retângulo a partir de um quadrado. Veja o fragmento abaixo

//aqui, criamos um retângulo com base em um quadrado, atendendo ao princípio do LSP
Retangulo retanguloQuadrado = new Quadrado
{
    Altura = altura,
    Largura = largura
};

Mas isso, tem um erro. Imagina que nossos sistema é usado para calcular as esquadrias em um prédio. E neste sistema eu preciso calcular a área para pedir que se corte uma esquadria quadrada. considerando que o quadrado tem seus lados iguais, e meu sistema aceita que eu passe altura x largura diferentes, mesmo para a classe “Quadrado”, eu tenho um erro de regra de negócio e de abstração.

Se eu crio uma classe retângulo a partir de uma classe “Quadrado” a área calculada ficará errada se eu passar os valores de “Altura” e “Largura” diferentes.

//aqui, criamos um retângulo com base em um quadrado, atendendo ao princípio do LSP
Retangulo retanguloQuadrado = new Quadrado
{
    Altura = 10,
    Largura = 5
};

//aqui acontece o erro, uma vez que eu passei 10x5=50. O meu calculo de área deveria considerar Altura X Largura igual para o calculo de quadrados.
Console.WriteLine($"A área correta do quadrado  é {Errado.Utility.CalculaArea.CalcularArea(retanguloQuadrado)}");

Pegando um exemplo real, nos artigos anteriores temos tratado as nossas classes de saldos como saldos de estoque. Imaginem que eu queira calcular o saldo financeiro. Em sua essência o cálculo é igual um menos o outro, entradas menos saídas. Só que de um lado eu estou falando de produtos, e de outro de valores monetários, cada qual com suas regras específicas.

Para terminar

Para atender ao princípio de Liskov, temos que garantir que as nossa classes genéricas, sejam substituíveis pelas nossas classes especializadas; Veja: Generalização x Especialização; Desta forma atendemos também o OCP.

Cuidado ao usar o princípio “É UM”, e preste muita atenção que este princípio se aplica ao comportamento da classe e não aos tipos ou subtipos. Valorize o polimorfismo.

SaberMais

Para saber mais.

O fonte utilizado aqui pode ser baixado pelo GITHub em https://github.com/desenvolvedores-net/ArtigoSOLID


É isso ai pessoal 🙂
Até o próximo
♦ Marcelo

OCP Open/ Closed Principle (Princípio aberto/fechado)

0
1 Estrela2 Estrelas3 Estrelas4 Estrelas5 Estrelas (1 votos, média: 5,00 de 5)
Loading...
10 de agosto de 2017

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…

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

SaberMais 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 Certo ou errado?

Abaixo vou colocar os códigos “ERRADO” e “CERTO” e ao final, analisar cada um.

Ferindo o OCP

errado

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.

certo

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

SRP Single Responsibility Principle (Princípio da Responsabilidade Única)

1
1 Estrela2 Estrelas3 Estrelas4 Estrelas5 Estrelas (1 votos, média: 5,00 de 5)
Loading...
9 de agosto de 2017


Veja o índice completo do tópico “S.O.L.I.D”

Olá pessoas …

Dando início a nossa saga de artigos, vamos iniciar com a primeira letra do acróstico, S que define a sigla SRP Single Responsibility Principle (Princípio da Responsabilidade Única).

Definição

Isto quer dizer que nossas classes devem ter apenas uma, e somente uma, razão para ser modificada. Se a classe possuir mais de um motivo para ser modificada, a mesma não é coesa e isso já fere os princípios da POO (Programação Orientada à Objetos).

Problemas ao ferir o primeiro princípio.

  • Dificuldade de compreensão, logo dificuldade de manutenção na classe;
  • Não permite reuso;
  • Alto acoplamento, a classe depende de conhecer outras e outras e mais outras classes para poder funcionar, o que dificulta a manutenção ao alterar as classes de dependência;

Imaginem um canivete suíço, para quem não sabe, é este ai abaixo, o pai dos canivetes suíços.

Canivete criado por John S. Holler por volta de 1880, na Alemanha e possui 100 funções.
As tampinhas só foram inventadas em 1891. 
Fonte: Google

Este canivete, se fosse uma aplicação, fere completamente o primeiro princípio. O da responsabilidade única. Em nossas aplicações cada objeto deve fazer apenas o que ele se propõe a fazer.

A ostra ostreia, o gato gateia, o vento venta
(Prof. Clóvis Barros Filho)

Nossas classes devem seguir esta linha de pensamento, se é uma classe de serialização, ela serializa, seja em banco de dados, arquivos, memória. Não importa, ela apenas serializa.
Se nossa classe é de validação, ela apenas valida.

Certo ou Errado?

Nos exemplos abaixo eu sempre colocarei o certo e o errado para compararmos as classes e as soluções propostas. Tentarei usar problemas e soluções do dia-a-dia.

Antes de começarmos vamos ver a imagem abaixo:

Como podemos ver, é muito mais simples criar da forma errada. Mas te trará problemas de manutenção de código, de reusabilidade de código, seu código será macarrônico e anêmico. Alto Acoplamento e baixa coesão. No momento da manutenção deste código, erros simples se tornam complexos de serem resolvidos.

Como ferir o princípio.

Veja o exemplo de código abaixo:

errado

Isto está errado.

/*
 * 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 Cliente
    {
        #region Private Methods

        private void Adicionar()
        {
            Validar();
            Console.WriteLine($"O cliente {Nome} foi inserido.");
        }

        private void Atualizar()
        {
            Validar();
            Console.WriteLine($"O cliente {Nome} foi atualizado.");
        }

        #endregion Private Methods

        #region Public Properties

        public string CNPJ { get; set; }
        public int Codigo { get; set; }
        public DateTime DataCadastro { get; set; }
        public string Email { get; set; }
        public string Endereco { get; set; }
        public string Nome { get; set; }

        #endregion Public Properties

        #region Public Methods

        public void EnviarEmail()
        {
            Console.WriteLine($"O e-mail foi enviado para o endereço {Email}");
        }

        public void Excluir(long id)
        {
            Console.WriteLine($"O cliente {Nome} foi excluído.");
        }

        public void Salvar()
        {
            if(Codigo != 0)
                Atualizar();
            else
                Adicionar();

            EnviarEmail();
        }

        public IList<Cliente> Selecionar(long? id = null)
        {
            List<Cliente> result = new List<Cliente>();

            return result;
        }

        public bool Validar()
        {
            if(String.IsNullOrWhiteSpace(Nome))
                throw new Exception("O nome é obrigatório");

            if(!Email.Contains("@"))
                throw new Exception("O e-mail não é válido");

            if(CNPJ.Length != 14)
                throw new Exception("O CNPJ não é válido");

            return true;
        }

        #endregion Public Methods
    }
}

A classe cliente escrita acima, tem diversos pontos que ferem o princípio da Responsabilidade Única:

  • Ela é uma classe que se faz sua própria serialização, como pode ver na linha 17 do método “Adicionar()”, linha 23 do método “Atualizar()” e assim nos demais métodos. Logo esta classe tem que conhecer a implementação de sua forma de serialização, seja banco de dados, XML, JSON, memória;
  • Esta classe é responsável por enviar um e-mail ao cliente, assim que o mesmo é cadastrado na aplicação. Imagina se você criar outras classes de pessoas, como Fornecedor, Usuário, Fabricante, e se fizer necessário o envio de e-mail ao se criar cada pessoa. Cada classe terá sua implementação do método “EnviarEmail()” e você terá que dar manutenção em cada método das classes de pessoas, se por um acaso o servidor for modificado, por exemplo;
  • Esta classe é  responsável por validar seus próprios dados. Em uma customização para clientes, esta abordagem seria trabalhosa, uma vez que cada cliente pode ter sua forma de validação de dados;

Como resolver o problema.

certo

Isto está correto.

/*
 * 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;

namespace Solid.Certo.Persistence
{
    public class Cliente: EntityBase
    {
        #region Public Properties

        public string CNPJ { get; set; }
        public DateTime DataCadastro { get; set; }
        public string Email { get; set; }
        public string Endereco { get; set; }
        public string Nome { get; set; }

        #endregion Public Properties

        #region Public Methods

        public void Excluir()
        {
            DBContext<Cliente>.Excluir(Codigo);
        }

        public void Salvar()
        {
            DBContext<Cliente>.Salvar(this);
        }

        #endregion Public Methods
    }
}

Como podem ver:

  • Esta classe não se “Valida”;
    • Para resolver este problema utilizamos o padrão de projeto “Strategy“. Que são:
      • StrategyDependencyManager: Responsável por fazer a injeção de dependência das estratégias na classe cliente;
      • IStrategy: Define o contrato para qualquer estratégia que não seja de persistência;
      • IPersistenceStrategy: Define o contrato para as estratégias de persistência dos dados;
      • StrategyBase: Abstração de estratégias para os tipos mais básicos;
      • PersistenceStrategyBase: Abstração para estratégias de persistências;
      • ClienteStrategy: Estratégia concreta para validação de clientes;
    • A validação do CNPJ passou a ser de responsabilidade do padrão Validator (Visitor Pattern).
      • IValidator: Contrato para classes de validação de tipos;
      • CNPJValidator: Classe concreta para validação do tipo CNPJ;
        • Estes tópicos, Visitor Pattern e Validators serão discutidos em outros artigos. Aqui iremos falar apenas do conceito S.O.L.I.D.
  • Ela não conhece a implementação de serialização, no caso, podemos perceber que seria em um banco de dados;
    • Este problema foi resolvido usando a classe DbContext, mas temos que tomar cuidado com esta abordagem, pois depende do framework de acesso à dados que você vai utilizar, o mais comum, para desenvolvimento .NET é o Entity Framework. Este link do StackOverflow; em Português; tem uma boa explicação sobre com prós e contras;
  • Ela não manda e-mails;
    • Quem decide se o e-mail deve ou não ser enviado é a estratégia de cada cliente, para isso a estratégia utiliza-se do serviço de e-mail para o envio do e-mail para o cliente.
      • EmailService: Classe de serviço de e-mail responsável por enviar e-mail e receber e-mail em toda a aplicação;

Como podemos ver, esta classe apenas faz o que lhe diz respeito. Manter os dados dos clientes.

Não se preocupe com os nomes de classes e conceitos explicados acima como solução para a forma correta. Durante o desenvolvimento dos demais artigos, irei explicar todas as classes e os princípios aplicados.

O fonte utilizado aqui pode ser baixado pelo GITHub em https://github.com/desenvolvedores-net/ArtigoSOLID

 


É isso ai pessoal 🙂
Até o próximo
♦ Marcelo

S.O.L.I.D

2
1 Estrela2 Estrelas3 Estrelas4 Estrelas5 Estrelas (1 votos, média: 5,00 de 5)
Loading...
31 de julho de 2017

Use este post como um índice para os demais artigos, basta clicar nos links para ler os artigos.

Olá galera…

Estou aqui mais uma vez com uma série de artigos, desta vez irei falar sobre S.O.L.I.D. Sim, eu sei que existem centenas de artigos sobre o assunto por ai, mas minha intenção aqui é aprender um pouco mais sobre o assunto com alguns exemplos em C#.

Afinal, o que é S.O.L.I.D.?

O padrão S.O.L.I.D. são cinco princípios que nos ajudam a desenvolver aplicações mais robustas e de fácil manutenção. Vamos entender o que é cada um dos princípios nos tópicos abaixo.

Estes princípios foram definidos pelo Robert. C. Martin, ou se preferirem, Uncle Bob, e datam do início do ano 2000.

S.O.L.I.D. é um acróstico para os seguintes acrônimos :

Para ler em detalhes cada uma das siglas, basta clicar na sigla ou em sua definição.

SRP

Single Responsibility Principle (Princípio da Responsabilidade Única)

note-taking Uma classe deve ter um único, e somente um, motivo para que possa ser modificada.
A class should have one, and only one, reason to change.

OCP

Open/ Closed Principle (Princípio aberto/fechado)

note-taking Você deve ser capaz de estender um comportamento de uma classe sem modificá-lo.
You should be able to extend a classes behavior, without modifying it.

LSP

Liskov Subtitution Principle (Princípio da Substituição de Liskov)

note-taking As classes derivadas devem poder substituir suas classes bases.
Derived classes must be substitutable for their base classes.

ISP

Interface Segregation Principle (Princípio de segregação de interface)

note-taking Interfaces específicas são melhores do que uma interface geral
Make fine grained interfaces that are client specific.

DIP

Dependency Inversion Principle (Princípio da inversão de dependência)

note-taking Dependa de uma abstração e não de uma implementação.
Depend on abstractions, not on concretions.

Atenção

Durante o desenvolvimento dos exemplos, não vou usar nenhum ORM, Framework ou qualquer outra ferramenta de apoio para não confundir a nossa cabeça, irei apenas aplicar os conceitos de S.O.L.I.D nos exemplos.

Os fontes utilizados como exemplo podem ser baixados pelo GITHub em https://github.com/desenvolvedores-net/ArtigoSOLID

SaberMais Para saber mais.

 


É isso ai pessoal 🙂
Até o próximo
♦ Marcelo