Introducción
Ester artículo será una expansión y mejora del anterior
Como evitar el uso del switch (1/2)
Básicamente se plantearan dos cambios significativos
- definir una clase base, donde poder definir código repetitivo
- el combobox resuelva de forma directa la instancia de calculo
Definir una clase base
Una de de los principales problemas que presenta a simple vista es el código similar en cada implementación de las clases de calculo, en la mayoría de los casos el bloque
es idéntico, salvando lo resaltado en círculos que varia en cada implementación, lo resaltado se trata justamente de las funciones de fecha que el plazo determina en cada caso
Ahora bien, como se podría evitar repetir código ?
Para poder lograrlo realizaremos varias modificaciones, la primer será cambiar la Interface por una clase abstract, esto nos permitirá definir una clase base que puede definir código reutilizable
public abstract class ResolverPlazo { public abstract PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota); protected PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota, Func<DateTime, int, DateTime> formulafechaFin, Func<DateTime, int, DateTime> formulafechaItem) { // //Se calculan la cantidad de cuotas // int cantcuotas = Convert.ToInt32(Math.Floor(monto / montocuota)); // // Se define la entidad de respuesta // PazoPagoResult result = new PazoPagoResult() { FechaFin = formulafechaFin(fechainicio, cantcuotas) }; // //crea la lista de Pagos // for (int cuota = 1; cantcuotas >= cuota; cuota++) { ItemPago item = new ItemPago() { Fecha = formulafechaItem(fechainicio, cuota), Monto = montocuota * cuota }; result.ListaPagos.Add(item); } return result; } }
Hay algunos puntos importantes por resaltar, el primero se define un método como abstract, cada clase concreta será responsabilidad desarrollarlo, pero además para ayudar se define un método adicional que actuaria como témplate para el calculo, define el código que detectamos como repetitivo y añade un punto de extensibilidad por medio del uso de Func<>
En los lugares donde se requiere especificidad que solo la clase concreta conoce se deja el lugar abierto para poder hacerlo, básicamente el Func<> es un delegado que recibe una fecha y un valor numérico, y espera como respuesta una fecha
Las clases concretas se ven ahora beneficiadas, reduciéndose notablemente el código
public class SinPlazoPago : ResolverPlazo { public override PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota) { return new PazoPagoResult() { FechaFin = fechainicio, ListaPagos = null }; } } public class UnSoloPago : ResolverPlazo { public override PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota) { return new PazoPagoResult() { FechaFin = fechainicio, ListaPagos = new List<ItemPago>() { new ItemPago(){ Fecha = fechainicio, Monto = monto } } }; } } public class Semanal : ResolverPlazo { public override PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota) { return base.Calcular(fechainicio, monto, montocuota, (fecha, cuotas) => fecha.AddDays(7 * cuotas), (fecha, cuota) => fecha.AddDays(7 * cuota)); } } public class Quincenal : ResolverPlazo { public override PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota) { return base.Calcular(fechainicio, monto, montocuota, (fecha, cuotas) => fecha.AddDays(15 * cuotas), (fecha, cuota) => fecha.AddDays(15 * cuota)); } } public class Mensual : ResolverPlazo { public override PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota) { return base.Calcular(fechainicio, monto, montocuota, (fecha, cuotas) => fecha.AddMonths(1 * cuotas), (fecha, cuota) => fecha.AddMonths(1 * cuota)); } } public class Bimestral : ResolverPlazo { public override PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota) { return base.Calcular(fechainicio, monto, montocuota, (fecha, cuotas) => fecha.AddMonths(2 * cuotas), (fecha, cuota) => fecha.AddMonths(2 * cuota)); } } public class Trimestral : ResolverPlazo { public override PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota) { return base.Calcular(fechainicio, monto, montocuota, (fecha, cuotas) => fecha.AddMonths(3 * cuotas), (fecha, cuota) => fecha.AddMonths(3 * cuota)); } } public class Semestral : ResolverPlazo { public override PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota) { return base.Calcular(fechainicio, monto, montocuota, (fecha, cuotas) => fecha.AddMonths(6 * cuotas), (fecha, cuota) => fecha.AddMonths(6 * cuota)); } } public class Anual : ResolverPlazo { public override PazoPagoResult Calcular(DateTime fechainicio, decimal monto, decimal montocuota) { return base.Calcular(fechainicio, monto, montocuota, (fecha, cuotas) => fecha.AddYears(1 * cuotas), (fecha, cuota) => fecha.AddYears(1 * cuota)); } }
Solo las dos primeras clases (que no tienes lógica en la calculo de los plazos de pago) quedaron como se definen originalmente, mientras que el resto hace uso de la funcionalidad definida en la clase base, nótese como las expresiones lambda en los parámetros del delegado permite variar el calculo de la fecha en cada caso.
Resolver de forma directa la clase de calculo
Este caso quizás no aplique en todas los casos ya que se requiere de entorno en donde los controles puedan conservar las instancias de los ítems con que con cargados, en este caso por tratarse de un ejemplo con Windows Application puede aplicarse, pero si seria un entorno web no se podria ya que los controles no conservan estado, se debería seguir usando la implementación original.
Lo que se pretende es poder quitar el método que usa el Dictionary<> para definir que clase concreta resolverá el calculo, el primer cambio se realiza en la clase que se usara para cargar el combo
public class Plazo { public int Id { get; set; } public string Desc { get; set; } public ResolverPlazo Instancia { get; set; } }
se agrega una propiedad que define la instancia de la clase que resuelve el calculo de plazos para ese ítem
El siguiente cambio impacta en la forma como se devuelve la lista de ítems, se define nueva la propiedad con la instancia concreta de cada implementación de calculo
public static class PlazosHelper { public static List<Plazo> ObtenerPlazos() { return new List<Plazo>() { new Plazo() { Id=1, Desc="Un solo pago", Instancia = new UnSoloPago() }, new Plazo() { Id=2, Desc="Semanal", Instancia = new Semanal() }, new Plazo() { Id=3, Desc="Quincenal", Instancia = new Quincenal() }, new Plazo() { Id=4, Desc="Mensual", Instancia = new Mensual() }, new Plazo() { Id=5, Desc="Bimestral", Instancia = new Bimestral() }, new Plazo() { Id=6, Desc="Trimestral", Instancia = new Trimestral() }, new Plazo() { Id=7, Desc="Semestral", Instancia = new Semestral() }, new Plazo() { Id=8, Desc="Anual", Instancia = new Anual() } }; } public static List<Plazo> ObtenerPlazosConItemOpcional() { List<Plazo> plazos = ObtenerPlazos(); plazos.Insert(0, new Plazo() { Id = 0, Desc = "<<<Seleccione>>>", Instancia = new SinPlazoPago() }); return plazos; } }
Solo queda cambiar la forma como se recupera el ítem del combo y se invoca el método de calculo
private void Calcular() { errorProv.Clear(); decimal monto = 0; if (!decimal.TryParse(txtMonto.Text, out monto)) { errorProv.SetError(txtMonto, "Debe ingresar un valor numerico"); return; } decimal montoporcuota = 0; if (!decimal.TryParse(txtMontoPorCuota.Text, out montoporcuota)) { errorProv.SetError(txtMontoPorCuota, "Debe ingresar un valor numerico"); return; } // // Recuperamos la instancia de la entidad bindeada al combo // ResolverPlazo calcularPlazo = ((Plazo)cmdPlazo.SelectedItem).Instancia; // // se invoca de forma directa la operacion de calcular // PazoPagoResult result = calcularPlazo.Calcular(dtpFechaInicio.Value, monto, montoporcuota); txtResultado.Text = result.FechaFin.ToShortDateString(); dgvListaPagos.DataSource = result.ListaPagos; }
La clave esta en estas dos líneas
// // Recuperamos la instancia de la entidad bindeada al combo // ResolverPlazo calcularPlazo = ((Plazo)cmdPlazo.SelectedItem).Instancia; // // se invoca de forma directa la operacion de calcular // PazoPagoResult result = calcularPlazo.Calcular(dtpFechaInicio.Value, monto, montoporcuota);
Como cada ítem del combo permite recuperar la clase con que fue creado, se puede castear al tipo concreto para así recuperar la instancia de la clase que posee la lógica de calculo de las cuotas, con eso se evita usa un método adicional que defina un diccionario de correspondencias entre un id y su instancia
Ya no se requiere el SelectedValue, porque no se trabaja con ningún id o código, sino que se accede directo a la instancia de la clase concreta, acortando las interacciones
Código
[C#]
|