Refactoring aplicando patrones

En otro post de este blog, habíamos visto la posibilidad de reescribir código que a nuestro juicio no estaba orientado a objetos. En esta ocasión, volvemos con un caso similar. Se trata de un anidamiento de sentencias condicionales IF basadas en configuraciones.
La idea principal es reemplazar todo este árbol de condiciones con una estructura orientada a objetos que permita poder introducir modificaciones de una manera mucho más simple.
Este caso en particular presentaba un método que buscaba un código GTIN y luego, según la parametrización del sistema debía validar el resultado obtenido.
No es la idea de este post explicar exactamente lo que significa un código GTIN. Bastará con explicar que dadas las legislaciones vigentes a la fecha en Argentina, es necesario obtener dicho código para poder realizar un comprobante de venta de manera electrónica. Tampoco es el interés de este post entrar en detalle sobre la arquitectura de la clase en la cual se halla implantado este método, sino, simplemente, el código hallado en él. 
El método en cuestión es el siguiente:

public class ComprobanteDeVentas
{
    private ComprobanteDetalle comprobanteDetalle;
    private BuscadorCodigosGTIN BuscadorCodigosGTIN;

    private void AsignarCodigoGTIN()
    {
        this.comprobanteDetalle.Item.CodigoGTIN = this.BuscadorCodigosGTIN.Obtener( this.comprobanteDetalle.Item );
        if ( Parametros.TipoDeWebServiceparafacturacionelectronicanacional == 2 )
            if ( !string.IsNullOrEmpty(this.comprobanteDetalle.Item.Articulo_PK ) && string.IsNullOrEmpty( this.comprobanteDetalle.Item.CodigoGTIN ) && Parametros.AvisarSiNoExisteCodigo )
                if ( Parametros.PermiteContinuar == 1)
                    Mensajes.Advertir( "No se encontró codigo GTIN para los datos ingresados" );
                else
                    throw new Exception( "No se encontró codigo GTIN para los datos ingresados" );
    }
}

Como se puede apreciar, no sólo hay una cuestión técnica de arquitectura, sino también una cuestión de difícil entendimiento del código debido a la aglutinación de condiciones y lo compleja de cada una de ellas.
Yendo al análisis arquitectónico de este código, se puede determinar que la validación de la obtención de un código GTIN vacío es un comportamiento que no debe ser responsabilidad del comprobante de ventas, sino de un componente y que además, dicho componente tiene tres claras validaciones dependiendo de los parámetros del sistema:
  1. No validar nada.
  2. Que se muestre un mensaje de advertencia pero que permita continuar.
  3. Que se lance una excepción y corte el flujo del programa.
Teniendo esto en cuenta, se procedió a la creación de tres clases nuevas para contener estos tres comportamientos (las cuales implementan una misma interfaz) y así aplicar el patrón Null object que resulta ser una derivación del patrón Strategy:

public interface IValidadorGTIN
{
    void Validar( Item item );
}

public class ValidadorGTINNoHaceNada : IValidadorGTIN
{
    public void Validar( Item item )
    {
        //No se lleva a cabo ninguna acción.
    }
}

public class ValidadorGTINAvisarSiEstaVacio : IValidadorGTIN
{
    public void Validar(Item item)
    {
        if (!string.IsNullOrEmpty(item.Articulo_PK) && string.IsNullOrEmpty( item.CodigoGTIN ) )
            Mensajes.Advertir("No se encontró codigo GTIN para los datos ingresados");
    }
}

public class ValidadorGTINNoDejaSeguirSiEstaVacio : IValidadorGTIN
{
    public void Validar(Item item)
    {
        if (!string.IsNullOrEmpty( item.Articulo_PK ) && string.IsNullOrEmpty( item.CodigoGTIN ) )
            throw new Exception("No se encontró codigo GTIN para los datos ingresados");
    }
}

Vemos que cada una de las clases tiene el método validar y que a su vez tienen una sola responsabilidad cada una. Todas tienen implementan la misma interfaz: un método llamado Validar que recibe un objeto Item a validar pero cada una la implementa de una manera diferente. La primer clase no hace nada. La segunda envía un mensaje a través del componente Mensajes. La tercera lanza una excepción.
El siguiente paso fue crear la lógica para que el ComprobanteDeVentas pueda recibir la instancia del validador que corresponde según el caso. Para ello tratamos de usar el patrón Factory Method para encapsular la lógica encargada de la inyección del componente correcto.

public class ValidadorGTINFactory
{
    public static IValidadorGTIN ObtenerInstancia()
    {
        IValidadorGTIN validador = new ValidadorGTINNoHaceNada();
        bool avisarSiVacio = Parametros.AvisarSiNoExisteCodigo;
        int permiteContinuar = Parametros.PermiteContinuar;

        if ( avisarSiVacio && permiteContinuar == 1 )
            validador = new ValidadorGTINAvisarSiEstaVacio();
        if ( avisarSiVacio && permiteContinuar == 0 )
            validador = new ValidadorGTINNoDejaSeguirSiEstaVacio();

        return validador;
    }
}

Por medio del uso del patrón Factory Method, se lleva a cabo la instanciación del objeto validador correspondiente para la parametrización del momento. Por defecto, se devuelve el componente que no hace nada. Luego, dependiendo de la parametrización del sistema, se instancia cualquiera de las otras clases. En el caso que llegare a surgir un nuevo caso o comportamiento, bastará con agregar el caso a esta clase e instanciar la clase correspondiente al nuevo caso de validación sin la necesidad de modificar la clase ComprobanteDeVentas, respetando así uno de los principios de SOLID donde cada clase debe tener una y sólo una responsabilidad (Single responsability principle).
Finalmente, realizamos los cambios necesarios en la clase ComprobanteDeVentas para que utilice las clases creadas. Para ello, creamos una variable de clase para contener al componente validador. El mismo es inyectado en la clase vía su constructor (*).

public class ComprobanteDeVentas
{
    private ComprobanteDetalle comprobanteDetalle;
    private BuscadorCodigosGTIN BuscadorCodigosGTIN;
    private IValidadorGTIN validador;

    public ComprobanteDeVentas( IValidadorGTIN validador )
    {
        this.validador = validador;
    }

    private void AsignarCodigoGTIN()
    {
        this.comprobanteDetalle.Item.CodigoGTIN = this.BuscadorCodigosGTIN.Obtener( this.comprobanteDetalle.Item );
        //Con esta línea de código se ve reemplazado el árbol de condiciones preexistentes.
        this.validador.Validar( this.comprobanteDetalle.Item );
    }
}

De esta manera, la lógica de validación del código GTIN obtenido, queda separada de la clase ComprobanteDeVentas y totalmente reutilizable para otros comprobantes. A este beneficio se suma la posibilidad de poder realizar tests individuales (TDD) de cada clase sin la necesidad de instanciar un comprobante de ventas o incluso poder generar mocks o fakes de las clases del componente validador en un test de la clase ComprobanteDeVentas.

(*) El Factory Method del validador, debe ser invocado desde el Factory Method que instancia la clase ComprobanteDeVentas

0 comentarios:

Publicar un comentario

Muchas gracias por leer el post y comentarlo.

 
Copyright 2009 Programación SOLIDa
BloggerTheme by BloggerThemes | Design by 9thsphere