domingo, 23 de septiembre de 2012

Como evitar el uso del switch (2/2)

 

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

imagen1

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#]
 

9 comentarios:

  1. Buenos días Leandro tengo una consulta respecto al uso de jquery.mask quisiera hacer lo siguiente:
    $("#variable").mask("99999" + "-" + "aa" + "-" + "9999"); , donde "aa" NO ES UN VALOR ESTÉTICO es un valor que cambia es decir que mi minimo valor de letras a ingresar es min=2 y mi maximo valor=7. Como podría hacer para que mi valor sea dinamico y pueda aceptar dichos valores O exite algo parecido a ello que cambie los valores estéticos a dinamicos=?

    ResponderEliminar
  2. hola Kely

    si la idea es que la mascara sea dinamica, podrias segun encesites cambiar el mask() que asignes usando jquery

    o sea si al principio define una mascara de 2 y ante determinada opcion debe pasar a 7, solo defines un mask() nuevo

    por supuesto esto vas a tener que hacerlo desde javascript, el dinamismo lo brindas tu cambiando la mascara segun la necesidad de cada momento

    saludos

    ResponderEliminar
  3. Gracias por su respuesta, pero no conprendo cuando me dice que pase un valor y haga otra que pase la opcion, ya que hice algo asi pero no funciona el Jquery.Mask. hice algo asi:
    jQuery(function($) {
    //var r="aaaaaa"
    //var valor = valida(valor)
    //$("#txtprueba03").mask("99999" + "-" + valor + "-" + "9999");
    var n = "99999"
    var y = "9999"
    var l = "aa"
    var t = "aaaaaa"
    // if (l >= "aa" ) {
    // $("#txtprueba03").mask(n + "-" + l + "-" + y, { placeholder: "_" });
    // }else if (l<="aaaaaa"){
    // $("#txtprueba03").mask(n + "-" + l + "-" + y, { placeholder: "_" });
    // }else {
    // alert("No esta en el rango");
    // }
    $("#txtprueba03").mask(n + "-" + l + "-" + y, { placeholder: "_" });
    $("#txtprueba03").mask("-" + t + "-" , { placeholder: "_" });

    });
    . Espero que me pueda ayudar ya que no logro que el valor "aa" sea dinamico y que no este haciendolo manualmente

    ResponderEliminar
  4. hola

    este txtprueba03 es un control html,no? porque si es asp.ent deebs usar el ClientID
    $("#<%=txtprueba03.ClientID%>")

    ademas porque defiens dos mascaras para el mismo control definiendo el .mask() u no debajo del otro, esta claro que solo aplicaria el ultimo que asignes

    saludos

    ResponderEliminar
  5. Si es en asp.net y mi aplicativo solo tiene un textbox lo que yo quiero es que el valor del centro que ingresa letras pueda modificarse hasta un maximo valor que es 7. Esta es mi logica :


    jQuery(function($) {
    var valor=valida(valor)
    $("#txtprueba03").mask("99999"+"-"+valida(valor)+"-"+"9999");
    });

    function valida(valor) {
    var sigla = "aa" // Mi valor minimo //es 2 y Mi maximo valor es 6
    var valor=sigla
    var min="a"
    var max = "aaaaaaaaa"
    var i="aa"
    for ( i = valor; i < max; i++) {
    if (valor > min && valor < max) {
    valor += valor // si esta en el //rango deberia de permitirme en //que mi valor ingresado se //MODIFIQUE
    } else {
    alert("Intentelo mas tarde");
    }
    return valor
    }





    Vuelvo a consultar Se puede hacer ello con Jquery.Mask O con AjaxControlToolkit:MaskedEditExtender =?

    ResponderEliminar
  6. hola Kely

    no se que quisiste hacer aqui

    jQuery(function($) {

    pero me parece bastante raro, si es la inicializacion deberia ser

    $(document).ready(function(){ ...

    despues lo de la mascara la verdad no sabria decirte, yo optaria por algo mas simple, aplica la mascara hasta donde puedas dejando el maximo permitido, despues si hay alguna otra restriccion lo validas con algun control de validacion ya se un control CustomValidator, o con asl validaciones de jquery

    saludos

    ResponderEliminar
  7. Hola Leandro
    Te consulto como podría modificar el método Calcular de la clase ResolverPlazo cuando al seleccionar por ejemplo pago semanal la primer cuota sea en la fecha seleccionada en "fechainicio" y no 7 días después como actualmente lo hace. Es decir si la fechainicio es 18/12/2013 me quede:
    18/12/2013 => 1 pago
    25/12/2013 => 2 pago
    ...
    y así sucesivamente Desde ya gracias.

    ResponderEliminar
  8. hola Augusto

    pero alli el metodo Calcular() tiene un parametro de fechainicio
    simplemente no le sumes los 7 dias entonces iniciara en la fecha definida en fechainicio

    basicamente no realices el AddDays() porque en tu caso no lo necesitas

    saludos

    ResponderEliminar
  9. AYUDA NECESITO DESARROLLAR LO SIGUIENTE:
    Gestión de un Zoológico.

    A continuación se presenta la jerarquía de clases que representa los animales de un posible zoológico familiar:




    La clase Animal es una clase abstract con cuatro atributos miembros protected:

    a) Un String indicando la especie (león, águila, abeja),
    b) Un String indicando el nombre del animal concreto (Simba, Maya, RinTinTin).
    c) Un double indicando el peso el kg.
    d) Un int indicando el número de jaula que se asigna al animal.

    Además, la clase Animal declara un método virtual queClaseDeAnimalEres() que habrá que definir en las clases derivadas.

    La clase Mamifero no añade nuevos atributos miembro, aunque deberá implementar el método queClaseDeAnimalEres().

    La clase Ave tiene dos nuevos atributos protected:
    a) Un String colorPlumaje indicando el color predominante y
    b) Un double indicando la alturaMaximaVuelo.

    La clase Insecto tiene un nuevo atributo miembro protected de tipo boolean llamada vuela que indica si el insecto vuela o no.

    Para realizar este ejercicio se pide lo siguiente:

    I. Crear las cuatro clases indicadas, con los correspondientes constructores y sobre carga de constructores, Como ayuda, se indica que el orden de los argumentos en el constructor parametrizado de la clase base es:

    public Animal(String especie, String nombre, double peso, int jaula){...}

    II. Definir los métodos llamados queClaseDeAnimalEres( ) en cada una de las clases derivadas de Animal. Este método no tiene valor de retorno (es void) ni argumentos.
    Debe ser capaz de mostrar por la pantalla la información correspondiente al animal de que se trate (ver el ejemplo), utilizando para ello la información almacenada en las variables miembro.
    Ejemplo:
    Soy un mamífero llamado: xxxxxxxx
    de la especie: xxxxxxxx
    Peso en Kg: xxx
    Estoy en la jaula: xx


    III. Crear una clase ejecutora llamada Zoologico. Pruebe la jerarquía de clases que implemento, tome en consideración que los animales de carne y hueso se guardan en jaulas.

    ResponderEliminar