none
Diseñando patrón Decorador RRS feed

  • Pregunta

  • Hola

    Trabajo en una app Windows Forms, EF, VS 2019, SQL Seerver 2014

    El problema viene de un hilo anterior Diseñando Lista de Precios

    Lo que intento es hacer un diseño correcto aplicando el patrón Decorador es es la modificación de mi diseño

    Tengo dudas en la entidad ProductoPrecio ¿creo que los atributos CantidadMinima y CantidadMaxima ya no van? 

    No se si el diseño esta bien y solvente las necesidades del negocio, una de ellas es la siguiente:

    Cuando se vende el servicio de impresión hay una regla de negocio cuando se vende de 0.01 cm a 0.49 cm se cobra 2.50 y a partir de 0.50 cm a 5.00 m el precio de venta es de 5.00 esto quiere decir que a partir de acá puedo usar x = cantidad * pvp, de 5.01 m a infinito se cobra 4.50 x = cantidad * pvp

    PvpMinimo queda en la entidad ProductoPrecio?

    Saludos!


    Pedro Ávila
    "El hombre sabio querrá estar siempre con quien sea mejor que él."
    Lima - Perú

    miércoles, 11 de diciembre de 2019 17:56

Respuestas

  • Buenas don Pedro, aquí aparezco según su solicitud.  Me pezcas con tiempo libre, jaja.

    El patrón decorator es un patrón que tiene por función SIMPLIFICAR una tarea que podría llegar a ser muy compleja, como muchos otros patrones.  Usar el patrón suele ser muy manejable y por eso es popular para estos casos.  El objetivo final del patrón es que al final, usted solamente tenga que hacer aglo como esto:

    factura.Total = items.Sum(x => x.CalcularPrecio());

    O algo similar, no?  La idea es que no importa cuánto cambie la lógica de precios, que esta línea de cálculo de total no tenga que estar tocándose a cada rato.

    Entonces si analizamos el objetivo final, vemos que la colección items debería ser polimórfica:  A veces son precios de mayorista, a veces de minorista, etc.

    De inmediato entonces pienso que IComponente necesita 2 cosas:  Un nuevo nombre, y más propiedades.  Algo como IDetalleFactura:

    public interface IDetalleFactura
    {
        decimal Cantidad { get; }
        Producto Producto { get; } //Producto sería una entidad que define el producto.
        Precio PrecioUnitario { get; } //Precio es otra entidad.
        decimal Total { get; } //En vez de CalcularTotal().
    }


    Entonces sus clases decoradoras ahora pueden usar tanto la cantidad como el producto en la lógica que utilizan para calcular el total y también para asignar un objeto Precio a la propiedad PrecioUnitario, que según parece es una tarea ligada al total.

    Entonces yo, muy en lo personal, tendría una clase base para todos los decoradores que representarán los detalles de la factura:

    public abstract class DecoradorDetalleFactura : IDetalleFactura
    {
        #region Propiedades
        protected IDetalleFactura Item { get; }
        public decimal Cantidad => Item.Cantidad;
        public Producto Producto => Item.Producto;
        public abstract Precio PrecioUnitario { get; }
        public virtual Total => Cantidad * PrecioUnitario.Valor;
        #endregion
    
        #region Constructores
        public DecoradorDetalleFactura(IDetalleFactura item)
        {
            Item = item;
        }
        #endregion
    }


    Luego todos los decoradores heredarían de esa clase base y solamente deben implementar la propiedad de precio unitario para todos aquellos productos cuyo total se calcula multiplicando un precio unitario por una cantidad, o bien implementar la propiedad de precio y hacer un override de la propiedad Total para implementar otra lógica distinta, como lo sería el caso de la venta de plotter.

    En el caso del plotter tendrías que obtener la información de precio que se ajusta tanto al producto como a la cantidad facturada de la base de datos, que sería el dato que se guarda en la propiedad de precio unitario.  Luego ya el total lo calculas según tus fórmulas.

    Ah y olvidaba:  Necesitarás código que decide qué decoradores aplicar.  Según veo tú estás pensando en un decorador para mayoristas y otro para minoristas.  En lo personal no lo haría así:  Pienso que es más sencillo tener un decorador único que obtiene el precio correcto a partir de la cantidad y del producto a vender.  ¿Por qué?  Porque si la cantidad o el producto se modifica, tendrás que eliminar el decorador y potencialmente poner uno distinto, esto cada vez que se edite la línea.  No me parece práctico.

    Me parece más práctico un decorador del modelo que tenga la lógica completa.  Puede que esto parezca extraño, porque pensaríamos que para qué quiero un decorador que siempre tengo que agregar, pero el decorador está encapsulando toda la lógica de negocio que dicta las escalas de precio.  Si algún día esa lógica de negocio cambia, pues nada más hay que cambiar el decorador.

    ¿Por qué esto no se parece a lo que usted esperaba?  Porque los ejemplos de decorador en cuanto a descuentos de mayoristas muy probablemente tienen valores que no dependen del producto.  Muchos ejemplos de este patrón son por descuento por un total mayor a XXX cantidad de dinero en el total de factura, etc.  No lidian con cosas como el producto individual de cada línea.


    Jose R. MCP
    My GIT Repositories | Mis Repositorios GIT


    jueves, 12 de diciembre de 2019 4:13
    Moderador
  • hola

    >>quiero aprender a usar el Pattern Decorator

    eso esta bien, pero no lo fuerces

    >>¿Pero si ya tengo todos los precios, porque no ponerlos de una vez y evitar a llamar mas rutinas de código?

    por lo mismo que respondi antes, estas pensando en datos y tablas

    cuando modelas patrones no hay tablas, los precios y reglas cuando aplicas se definen en codigo

    por eso decia que lo resuelvas con un linq

    >>Hasta donde he llegado con la implementación de Decorador si es que lo hay, ¿de que manera lo debo de resolver?

    no entiendo porque realizas un foreach si podrias filtrarlo directo con un linq y ver que precio aplica

    saludos


    Leandro Tuttini

    Blog
    MVP Profile
    Buenos Aires
    Argentina

    jueves, 12 de diciembre de 2019 3:55

Todas las respuestas

  • No seria manejar el tope?

    TABLA PVP

    idPVP

    PvpInicio(1cm,51cm,5001)

    PvpTope(50cm ,5000cm,Null )

    PvpPrecio(2.50,5.00,4.50) Un poco raro los precios xd

    Pero es mejor manejarlo en una entidad aparte pues forma parte de un proceso del negocio (Siempre tiende a variar el precio o añadir un nuevo rango y al sacar un reporte se descuadraria)

    Ojo el PVPPrecio es el actual y el Precio de tu tabla seria el precio que se manejo en al hacer el insert 


    Si necesitas ayuda sube tu avance de otro modo no puedo ayudarte , Suerte!


    miércoles, 11 de diciembre de 2019 19:03
  • hola

    Creo que como lo venis planteando no lo estas pensando en ningun patron en concreto, sino mas bien en resolverlo en cuanto a datos

    Lo que si veo incorrecta es la relacion de navegacion entre ListaPrecio y PrecioPorductos porque esta deberia ser una relacion muchos a muchos

    Una lista de precios define los precio de muchos productos 

    Quizas con una query linq podrias resolver que precio aplicar

    var query = from listaprecio in db.ListaPrecio
                       from precio in listaprecio.PrecioProductos
                        where (listaprecio.FechaIncio >= fecha && listaprecio.FechaFin <= fecha)
                                  && precios.Producto = productos
                                  && (precios.CantidadMinima > cantidad && precios.CantidadMaxima < cantidad)
                       select precio;
    
    var precio = query.FirstOrDefault();

    la idea es poder resolver el precio que sacarias de Pvp para la formula

    saludos


    Leandro Tuttini

    Blog
    MVP Profile
    Buenos Aires
    Argentina

    miércoles, 11 de diciembre de 2019 22:51
  • Hola @Leandro

    Tengo otro diseño, quiero aprender a usar el Pattern Decorator

    Voy a decorar la entidad ProductoPrecio con MontoMinorista, MontoMayorista, MontoMinimo según sea la necesidad.

    Implementación de código

    public class SdMontoMayorista : IComponente
        {
            private readonly IComponente _decoramosA;
            public SdMontoMayorista(IComponente componente)
            {
                _decoramosA = componente;
            }
    
            public decimal CalcularPrecio(int productoId, decimal cantidad)
            {
                throw new NotImplementedException();
            }
        }


    MontoMinorista

    public class SdMontoMinorista : IComponente
        {
            private readonly IComponente _decoramosA;
    
            public SdMontoMinorista(IComponente componente)
            {
                _decoramosA = componente;
            }
    
            public decimal CalcularPrecio(int productoId, decimal cantidad)
            {
                throw new NotImplementedException();
            }
        }


    MontoMinimo

    public class SdMontoMinimo : IComponente
        {
            private readonly IComponente _decoramosA;
    
            public SdMontoMinimo(IComponente componente)
            {
                _decoramosA = componente;
            }
    
            public decimal CalcularPrecio(int productoId, decimal cantidad)
            {
                throw new NotImplementedException();
            }
        }

    SdProductoPrecio

    public class SdProductoPrecio : IComponente
        {
            
            public SdProductoPrecio(ProductoPrecio entity)
            {
                _entity = entity;
            }
    	public decimal CalcularPrecio(int productoId, decimal cantidad)
            {
                //var list = Filter(x => x.ProductoId == productoId).ToList();
    
                return 0M;
            }

    Según lo que tengo por ejemplo tengo un servicio que se llama impresión y el cual tiene tres precios diferentes.

    Entonces con este código traigo todos los precios que tenga el producto. Claro que con el filtro y el if puedo definir con que decorar ProductoPrecio para obtener el precio que necesito para ponerlo en la formula.

    ¿Pero si ya tengo todos los precios, porque no ponerlos de una vez y evitar a llamar mas rutinas de código?

    Hasta donde he llegado con la implementación de Decorador si es que lo hay, ¿de que manera lo debo de resolver?

    Saludos!

    var productoPrecioList = _productoPrecioRepository
                                .Filter(x => x.ProductoId == entity.ProductoId).ToList();
    
                            foreach (var item in productoPrecioList)
                            {
                                if (item.ImporteMinimo != null)
                                {
                                    if (entity.Cantidad >= item.CantidadMinima && entity.Cantidad <= item.CantidadMaxima)
                                    {
                                        entity.ProductoPrecioId = item.ProductoPrecioId;
                                        entity.Precio = Convert.ToDecimal(item.ImporteMinimo);
                                        importe = Convert.ToDecimal(item.ImporteMinimo);
                                        entity.SubTotalIva = entity.Importe;
                                        entity.SubTotalIva = Math.Round(importe * 10000M / (100M + entity.Iva)) / 100M;
                                        entity.ValorIva = importe - entity.SubTotalIva;
                                        _ventaRepository.Agregar(entity);
                                    }
                                }
                                else
                                {
                                    if (entity.Cantidad >= item.CantidadMinima && entity.Cantidad <= item.CantidadMaxima)
                                    {
                                        entity.ProductoPrecioId = item.ProductoPrecioId;
                                        entity.Precio = item.Pvp;
                                        entity.SubTotalIva = entity.Importe;
                                        entity.SubTotalIva = Math.Round(entity.Importe * 10000M / (100M + entity.Iva)) / 100M;
                                        entity.ValorIva = entity.Importe - entity.SubTotalIva;
                                        _ventaRepository.Agregar(entity);
                                    }
                                }
                            }

    sino mas bien en resolverlo en cuanto a datos

    Los precios lo tengo que obtener de la base de datos, no lo puedo quemar en el código, ¿como lo implementarías ya conociendo lo que necesito?

    Saludos!


    Pedro Ávila
    "El hombre sabio querrá estar siempre con quien sea mejor que él."
    Lima - Perú


    jueves, 12 de diciembre de 2019 0:51
  • hola

    >>quiero aprender a usar el Pattern Decorator

    eso esta bien, pero no lo fuerces

    >>¿Pero si ya tengo todos los precios, porque no ponerlos de una vez y evitar a llamar mas rutinas de código?

    por lo mismo que respondi antes, estas pensando en datos y tablas

    cuando modelas patrones no hay tablas, los precios y reglas cuando aplicas se definen en codigo

    por eso decia que lo resuelvas con un linq

    >>Hasta donde he llegado con la implementación de Decorador si es que lo hay, ¿de que manera lo debo de resolver?

    no entiendo porque realizas un foreach si podrias filtrarlo directo con un linq y ver que precio aplica

    saludos


    Leandro Tuttini

    Blog
    MVP Profile
    Buenos Aires
    Argentina

    jueves, 12 de diciembre de 2019 3:55
  • Buenas don Pedro, aquí aparezco según su solicitud.  Me pezcas con tiempo libre, jaja.

    El patrón decorator es un patrón que tiene por función SIMPLIFICAR una tarea que podría llegar a ser muy compleja, como muchos otros patrones.  Usar el patrón suele ser muy manejable y por eso es popular para estos casos.  El objetivo final del patrón es que al final, usted solamente tenga que hacer aglo como esto:

    factura.Total = items.Sum(x => x.CalcularPrecio());

    O algo similar, no?  La idea es que no importa cuánto cambie la lógica de precios, que esta línea de cálculo de total no tenga que estar tocándose a cada rato.

    Entonces si analizamos el objetivo final, vemos que la colección items debería ser polimórfica:  A veces son precios de mayorista, a veces de minorista, etc.

    De inmediato entonces pienso que IComponente necesita 2 cosas:  Un nuevo nombre, y más propiedades.  Algo como IDetalleFactura:

    public interface IDetalleFactura
    {
        decimal Cantidad { get; }
        Producto Producto { get; } //Producto sería una entidad que define el producto.
        Precio PrecioUnitario { get; } //Precio es otra entidad.
        decimal Total { get; } //En vez de CalcularTotal().
    }


    Entonces sus clases decoradoras ahora pueden usar tanto la cantidad como el producto en la lógica que utilizan para calcular el total y también para asignar un objeto Precio a la propiedad PrecioUnitario, que según parece es una tarea ligada al total.

    Entonces yo, muy en lo personal, tendría una clase base para todos los decoradores que representarán los detalles de la factura:

    public abstract class DecoradorDetalleFactura : IDetalleFactura
    {
        #region Propiedades
        protected IDetalleFactura Item { get; }
        public decimal Cantidad => Item.Cantidad;
        public Producto Producto => Item.Producto;
        public abstract Precio PrecioUnitario { get; }
        public virtual Total => Cantidad * PrecioUnitario.Valor;
        #endregion
    
        #region Constructores
        public DecoradorDetalleFactura(IDetalleFactura item)
        {
            Item = item;
        }
        #endregion
    }


    Luego todos los decoradores heredarían de esa clase base y solamente deben implementar la propiedad de precio unitario para todos aquellos productos cuyo total se calcula multiplicando un precio unitario por una cantidad, o bien implementar la propiedad de precio y hacer un override de la propiedad Total para implementar otra lógica distinta, como lo sería el caso de la venta de plotter.

    En el caso del plotter tendrías que obtener la información de precio que se ajusta tanto al producto como a la cantidad facturada de la base de datos, que sería el dato que se guarda en la propiedad de precio unitario.  Luego ya el total lo calculas según tus fórmulas.

    Ah y olvidaba:  Necesitarás código que decide qué decoradores aplicar.  Según veo tú estás pensando en un decorador para mayoristas y otro para minoristas.  En lo personal no lo haría así:  Pienso que es más sencillo tener un decorador único que obtiene el precio correcto a partir de la cantidad y del producto a vender.  ¿Por qué?  Porque si la cantidad o el producto se modifica, tendrás que eliminar el decorador y potencialmente poner uno distinto, esto cada vez que se edite la línea.  No me parece práctico.

    Me parece más práctico un decorador del modelo que tenga la lógica completa.  Puede que esto parezca extraño, porque pensaríamos que para qué quiero un decorador que siempre tengo que agregar, pero el decorador está encapsulando toda la lógica de negocio que dicta las escalas de precio.  Si algún día esa lógica de negocio cambia, pues nada más hay que cambiar el decorador.

    ¿Por qué esto no se parece a lo que usted esperaba?  Porque los ejemplos de decorador en cuanto a descuentos de mayoristas muy probablemente tienen valores que no dependen del producto.  Muchos ejemplos de este patrón son por descuento por un total mayor a XXX cantidad de dinero en el total de factura, etc.  No lidian con cosas como el producto individual de cada línea.


    Jose R. MCP
    My GIT Repositories | Mis Repositorios GIT


    jueves, 12 de diciembre de 2019 4:13
    Moderador
  • Para lo que planteas siempre hay varias soluciones posibles

    quizas Decorator no sea el mas adecuando, hay otros como ser Chain of Resposability

    Chain of Responsibility in a real world refactoring example

    se puede aplicar de varias formas, pero en este ejemplo lo hace definiendo Reglas

    Lo unico que lo hace para evaluar la aplicacion de estilos visuales, no encontre algo integral que aplique reglas sobre calculos

    Entonces lo que habria que ver es como cargas dinamicamente las reglas tomando los datos de la db, asignandolas a SetNext()

    y asi cuando corres las reglas podras ver cual aplica y esa ejecutarla para obtener el monto que debes cobrar

    Decorator no tien eso de evaluar algo para ver si aplica, sino que simplemente lo pone como una capa a la entidad existente, pero la logica para ver si debes ponerla o no, eso no lo define

    Por eso hay que entender he ir probando, no debes forzar un patron si vez que no aplica correctamente para resolver el problema

    Patrón de diseño: Chain of Responsability

    saludos


    Leandro Tuttini

    Blog
    MVP Profile
    Buenos Aires
    Argentina

    jueves, 12 de diciembre de 2019 15:03