Refactoring State/Strategy

Este post nació a partir de una consulta en la lista de Patrones del Grupo de Usuarios Microsoft.

En mi opinión y partiendo de la poca información que puede intercambiarse en las listas sobre el problema concreto, una de las posibilidades es el uso combinado de los patrones State/Strategy (un refactoring definido por Fowler en su libro, un esquema puede verse aquí).

Brevemente, Leandro necesitaba calcular un impuesto sobre una transacción y el algoritmo de cálculo depende del tipo de cliente y de algun dato adicional (supongamos que entendí correctamente el problema).

La condición para aplicar State es que tenga un caso en el cual el comportamiento del cliente (en este caso, el tipo de liquidador de impuesto) dependa del estado (de un estado o situacion) que el cliente tenga en ese momento. Otra de las condiciones es tener un switch que reemplazar.

Bueno, ahi va algo de codigo (escrito directamente en el blog, sería raro que compile!!!)…

// La clase Cliente
public class Cliente {
   // Este es el estado en cuestion, en principio puede ser privado
   private TipoCliente tipoCliente;

   // Estamos suponiendo, tambien, que cuando se crea el cliente
   // queda ya configurado el tipo, aunque nada impide que
   // este estado cambie durante la vida del cliente.
   public Cliente( TipoCliente tipoCliente ) {
      this.tipoCliente = tipoCliente;
   }
}

// Ahora definimos el tipo, una clase abstracta
public class TipoCliente {
}

// Un tipo de cliente concreto
public class TipoClienteExtranjero : TipoCliente {
}

// Otro tipo de cliente concreto
public class TipoClienteEmpresa : TipoCliente {
}

Bien, hasta aca tenemos la estructura basica armada, ahora veamos como resolvemos el tema de obtener el tipo de calculador de impuesto.

// Primero definimos la interfaz para el calculador
public interface ICalculadorImpuesto {
   // Este metodo deberia calcular el impuesto sobre
   // la transaccion y devolver un objeto que defina
   // el impuesto aplicado (no defini estas dos clases)
   Impuesto CalcularImpuesto( Transaccion trx );
}

// Ahora definimos un calculador facil, para empresas extranjeras
public class CalculadorImpuestoExento : ICalculadorImpuesto {
   public Impuesto CalcularImpuesto( Transaccion trx ) {
      // Aca, de paso aplicamos el patron SpecialCase,
      // algo parecido al DateTime.MinValue
      return Impuesto.Zero;
   }
}

Esta bien, hagamos uno un poco mas complejo

// El que calcula el impuesto a los movimiendos de debito
// y credito para ctas ctes...
public class CalculadorImpuestoLey25413CtaCte : ICalculadorImpuesto {
   public Impuesto CalcularImpuesto( Transaccion trx ) {
      return new Impuesto( trx.Monto * 0.012 );
   }
}

// y el impuesto a los movimiendos de debito y credito para
// cajas de ahorro
public class CalculadorImpuestoLey25413CA : ICalculadorImpuesto {
   public Impuesto CalcularImpuesto( Transaccion trx ) {
      return new Impuesto( trx.Monto * 0.006 );
   }
}

Ahora apliquemos esto en nuestra clase Cliente

// La clase Cliente
public class Cliente {
   ...
   // Este metodo delega en el metodo del tipo de cliente
   public ICalculadorImpuesto ObtenerCalculadorImpuesto() {
      return tipoCliente.ObtenerCalculadorImpuesto( this );
   }
   ...
}

Falta ahora modificar las clases de tipos de cliente

// En la clase abstracta
public class TipoCliente {
   public abstract ICalculadorImpuesto ObtenerCalculadorImpuesto( Cliente cliente );
}

// Un tipo de cliente concreto
public class TipoClienteExtranjero : TipoCliente {
   public override ICalculadorImpuesto ObtenerCalculadorImpuesto( Cliente cliente ) {
      // Aqui devuelvo el claculador que necesito
      // Para extranjeros, exento, por ejemplo
      return new CalculadorImpuestoExento();
   }
}

// Otro tipo de cliente concreto
public class TipoClienteEmpresa : TipoCliente {
   public override ICalculadorImpuesto ObtenerCalculadorImpuesto( Cliente cliente ) {
   // Aqui devuelvo el claculador que necesito
   // Aqui vemos el caso en que el State consulta al
   // objeto que lo contiene para decidir.
   // ( implementacion sencilla! )
   if ( cliente.TipoCuenta == "CtaCte" )
      return new CalculadorImpuestoLey25413CtaCte();
   else
      return new CalculadorImpuestoLey25413CA();
   }
}

Por ultimo, veamos como se usaria esto en el ejemplo que se me ocurre, por ejemplo, en metodo que tiene que calcular el impuesto de la transaccion:

public void RegistraTransaccion( Cliente cliente, Transaccion trx ) {
   ICalculadorImpuesto ci = cliente.ObtenerCalculadorImpuesto();
   Impuesto imp = ci.CalcularImpuesto( trx );
   RegistrarTansaccionEImpuesto( trx, imp );
}

Bueno, espero que esto ayude. Aqui el patron State esta en la familia de clases TipoCliente y el Strategy en el calculador de impuestos.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *