Ainda não tem acesso? Estude com a gente! Matricule-se
Ainda não tem acesso? Estude com a gente! Matricule-se

Solucionado (ver solução)

Encadear descontos usando lista

Gostaria de utilizar uma lista para adicionar todos os descontos e setar os próximos com base nisso, a implementação correta seria essa?

    public class CalculadorDeDescontos
    {

        public IList<Desconto> Descontos { get; private set; }

        public double Calcula(Orcamento orcamento)
        {
            //-- Adicionar os tipos de descontos existentes a lista para serem encadeados
            this.Descontos.Add(new DescontoPorCincoItens());
            this.Descontos.Add(new DescontoPorMaisDeQuinhentosReais());

            this.Descontos.Add(new SemDesconto()); //-- Importante! Deve ser o último!

            //-- Definir a cadeia de execução dos descontos.
            for ( int contador = 0; contador < Descontos.Count; contador++)
            {
                //-- Verificar se não é último desconto ( Sem desconto )
                if ( contador + 1 < Descontos.Count) 
                {
                    //-- Definir que o próximo item da lista é o desconto a ser executado
                    Descontos[contador].Proximo = Descontos[contador + 1];
                }
            }

            //-- Executar o primeiro desconto e os demais serão executados em cadeia
            return Descontos[0].Desconta(orcamento);
        }
    }
8 respostas

Eu faria uma mudança no seu laço, não precisa do if dentro do laço se você fizer a condição correta:

for (int contador = 0; contador < Descontos.Count - 1; contador++)
{
    Descontos[contador].Proximo = Descontos[contador + 1];
}

Como você quer pular o último item, basta percorrer só até o penúltimo subtraindo um do tamanho da sua lista =)

Porém, já que seu objeto Desconto tem uma referência para outro desconto, você não precisa de uma lista. Acho que algo assim fica um pouco mais legível. Claro que isso não é super escalável, mas o método a que você chegou também pode ter seus problemas no futuro.

public double Calcula(Orcamento orcamento)
{
    Desconto porCinco = new DescontoPorCincoItens();
    porCinco.Proximo = new DescontoPorMaisDeQuinhentosReais();

    Desconto porMaisDeQuinhentos = porCinco.Proximo;
    porMaisDeQuinhentos.Proximo = new SemDesconto();

    return porCinco.Desconta(orcamento);
}

O if é desnecessário mas foi intencional, para reforçar que o último não vai receber, por isso os comentários, em um código que não tivesse esse IF seria mais difícil de explicar que o último não deve receber o próximo.

Sobre sua segunda sugestão, achei mais limpa realmente, mas é mais trabalhosa por continuar obrigando a definir os próximos desconto por desconto.

Acho que para resolver isso teria que definir um construtor nos descontos para definir o proximo e criar todos os descontos em um único código.

Ae ficaria algo assim:

public double Calcula(Orcamento orcamento)
{
    Desconto executaDesconto =  new DescontoPorCincoItens(
                                new DescontoPorMaisDeQuinhentosReais(
                                new SemDesconto( ) ) );

    return executaDesconto.Desconta(orcamento);
}

Outra alternativa é definir um setter pro atributo Proximo, que ao invés de void retorna um Desconto.

Desconto é uma interface? Pode ser uma classe abstrata? Ficaria algo semelhante a isso.

abstract class Desconto {
    public Desconto Proximo;
    public Desconto SetProximo(Desconto proximo) {
        this.Proximo = proximo;
        return this.Proximo;
    }
}
Desconto primeiro = new DescontoPorCincoItens();
primeiro.SetProximo(new DescontoPorMaisDequinhentosReais())
    .SetProximo(new OutroDesconto())
    .SetProximo(new MaisUmDesconto())
    .SetProximo(new SemDesconto());

Estou com problema ao executar:

    public class CalculadorDeDescontos
    {

        public IList<Desconto> ListaDescontos { get; private set; }

        public double Calcula(Orcamento orcamento)
        {
            //-- Adicionar os tipos de descontos existentes a lista para serem encadeados
            this.ListaDescontos.Add(new DescontoPorCincoItens() );
            this.ListaDescontos.Add(new DescontoPorMaisDeQuinhentosReais() );

            this.ListaDescontos.Add(new SemDesconto() ); //-- Importante! Deve ser o último!

            //-- Definir a cadeia de execução dos descontos.
            for ( int contador = 0; contador < ListaDescontos.Count; contador++)
            {
                //-- Verificar se não é último desconto ( Sem desconto )
                if ( contador + 1 < ListaDescontos.Count) 
                {
                    //-- Definir que o próximo item da lista é o desconto a ser executado
                    ListaDescontos[contador].Proximo = ListaDescontos[contador + 1];
                }
            }

            //-- Executar o primeiro desconto e os demais serão executados em cadeia
            return ListaDescontos[0].Desconta(orcamento);
        }
    }

O erro é nessa linha:

this.ListaDescontos.Add(new DescontoPorCincoItens() );

O erro é esse: {"Referência de objeto não definida para uma instância de um objeto."}

O código completo está aqui: https://github.com/sambomb/DesignPatterns

solução

Você precisa inicializar sua lista de descontos! Pode ser no construtor do CalculadorDeDescontos ou direto na declaração da variável se isso for possível!

Como eu posso fazer isso?

Achei:

ListaDescontos = new List<Desconto>();

E aí, tudo funcionou corretamente? =)

Se sim, lembre-se de marcar a resposta que te ajudou como solução do tópico!

Quer mergulhar em tecnologia e aprendizagem?

Receba a newsletter que o nosso CEO escreve pessoalmente, com insights do mercado de trabalho, ciência e desenvolvimento de software