Principales respuestas
Diseñando patrón Decorador

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ú
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- Editado webJoseModerator jueves, 12 de diciembre de 2019 4:20
- Propuesto como respuesta Pablo RubioModerator jueves, 12 de diciembre de 2019 17:06
- Marcado como respuesta Pedro Ávila jueves, 12 de diciembre de 2019 17:12
-
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- Propuesto como respuesta Pablo RubioModerator jueves, 12 de diciembre de 2019 17:06
- Marcado como respuesta Pedro Ávila jueves, 12 de diciembre de 2019 17:12
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!
- Editado DAMN1Self miércoles, 11 de diciembre de 2019 19:04
- Propuesto como respuesta Pablo RubioModerator miércoles, 11 de diciembre de 2019 21:57
-
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- Propuesto como respuesta Pablo RubioModerator jueves, 12 de diciembre de 2019 17:05
-
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ú- Editado Pedro Ávila jueves, 12 de diciembre de 2019 2:02
-
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- Propuesto como respuesta Pablo RubioModerator jueves, 12 de diciembre de 2019 17:06
- Marcado como respuesta Pedro Ávila jueves, 12 de diciembre de 2019 17:12
-
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- Editado webJoseModerator jueves, 12 de diciembre de 2019 4:20
- Propuesto como respuesta Pablo RubioModerator jueves, 12 de diciembre de 2019 17:06
- Marcado como respuesta Pedro Ávila jueves, 12 de diciembre de 2019 17:12
-
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- Propuesto como respuesta Pablo RubioModerator jueves, 12 de diciembre de 2019 17:06