[Tutorial] Clean Code: Switch | Parte 8

En anteriores post vimos las bifurcaciones If. Ahora veremos otro tipo de bifurcación, Switch. Esta es un poco más compleja de utilizar y factorizar. Manos a la obra!

Switch

Los switch son muy usados por programadores con menos experiencia en programación orientada a objetos o se encuentran muy acostumbrados a la programación del tipo funcional. Veamos algunos problemas y cómo refactorizar Switch. Nuestro ejemplo:

using System;

namespace CleanCode.Switch
{
    public class MonthlyBilling
    {
        public float CallCost { get; set; }
        public float SmsCost { get; set; }
        public float TotalCost { get; set; }

        public void Generate(MonthlyUsage usage)
        {
            switch (usage.Customer.Type)
            {
                case CustomerType.PayAsYouGo:
                    CallCost = 0.12f * usage.CallMinutes;
                    SmsCost = 0.12f * usage.SmsCount;
                    TotalCost = CallCost + SmsCost;
                    break;

                case CustomerType.Unlimited:
                    TotalCost = 54.90f;
                    break;

                default:
                    throw new NotSupportedException("The current customer type is not supported");
            }
        }
    }
}

Podemos ver un método Monthly Statement. Tiene las propiedades con los costos de una factura de teléfono y un método que recibe como parámetro MonthlyUsage. MonthlyUsage tiene una propiedad cliente, el total de minutos consumidos y el total de SMS enviados:

    public class MonthlyUsage
    {
        public Customer Customer { get; set; }
        public int CallMinutes { get; set; }
        public int SmsCount { get; set; }
    }

Nuestro método Generate tiene un Swtich. Dependiendo del tipo de Cliente (customers) hará uno u otra cosa. Si el cliente es del tipo PasAsYouGo hará unos cálculos, pero, sí el del tipo Unlimited, calculará de otra forma. Si no está dentro de estos 2 tipos, devolverá una excepción.

Primer problema:  si necesitamos agregar un nuevo tipo de cliente debemos modificar el código. Esto implicaría compilar y distribuir nuevamente nuestro código por cada nuevo tipo cliente que sea agrega. Esto rompería el principio de Open Close, abierto para extender, cerrado para modificaciones. Si no respetamos este principio y modificamos nuestro código, seguramente algo se romperá o dejara de funcionar.

Segundo problema: el método puede hacerse cada vez más largo cada vez que agregamos un nuevo tipo. Nunca podremos saber qué cantidad de tipos de clientes pueden existir en un futuro dando como resultado un código muy extenso.

Tercer problema:  al tener declaraciones asociadas a tipos de objetos es muy posible que estas estén por todo el código con diferentes funcionalidades. Inclusive, diferentes reglas para cada tipo de cliente. Te preguntaras ¿Cual es la solución a esto? una simple palabra: Polimorfismo. ¿Que quiere decir esto? que en lugar de tener una clase de cliente vamos a tener un jerarquía de clientes, una clase padre y esta tendrá sus derivados.

Comencemos a refactorizar. Lo primero que debemos cambiar es la alta dependencia de la variable usage. De esta variable dependemos para hacer los cálculos. Cuando vemos que una variable así, seguramente es porque debería estar encapsulada en una clase.

El principio de encapsulación dice que si queremos tener datos y métodos u operaciones que trabajen juntos deben pertenecer a la misma clase. Entonces, este método no pertenece a la clase donde se encuentra ubicada. Lo moveremos a MonthlyUsage donde debe pertenecer. El método Generate, ahora, no recibirá nada, solamente tendrá como salida MonthlyStatement.

    public class MonthlyUsage
    {
        public Customer Customer { get; set; }
        public int CallMinutes { get; set; }
        public int SmsCount { get; set; }

        public void Generate(MonthlyStatement monthlyStatement)
        {

            var stament = new MonthlyStatement();

            switch (usage.Customer.Type)
            {
                case CustomerType.PayAsYouGo:
                    stament.CallCost = 0.11f * usage.CallMinutes;
                    stament.SmsCost = 0.13f * usage.SmsCount;
                    stament.TotalCost = stament.CallCost + stament.SmsCost;
                    break;

                case CustomerType.Unlimited:
                    stament.TotalCost = 45.90f;
                    break;

                default:
                    throw new NotSupportedException("The current customer type is not supported");
            }
        }
    }

Hemos mejorado nuestro código, pero todavía depende de un tipo de cliente. También vemos que este fragmento de código debería pertenecer a un clase Customer. Vamos a moverlo a una nueva clase.

public class Customer
{
    public CustomerType Type { get; set; }

     public void Generate(MonthlyStatement monthlyStatement)
    {

        var stament = new MonthlyStatement();

        switch (usage.Customer.Type)
        {
            case CustomerType.PayAsYouGo:
                stament.CallCost = 0.11f * usage.CallMinutes;
                stament.SmsCost = 0.13f * usage.SmsCount;
                stament.TotalCost = stament.CallCost + stament.SmsCost;
                break;

            case CustomerType.Unlimited:
                stament.TotalCost = 45.90f;
                break;

            default:
                throw new NotSupportedException("The current customer type is not supported");
        }
    }
}

Bien, ahora empezaremos a eliminar el switch. Como comenté anteriormente, vamos a usar la capacidad de polimorfismos para modificar nuestro código. Primero crearemos la clase PayAsYouGoCustomer qué heredada de Customer. En su consctructor configuraremos que el tipo es PayAdYouGo.

public class  PayAsYouGoCustomer: Customer{
    public PayAsYouGoCustomer(){
        Type = CustomerType.PayAsYouGo;
    }
}

Ahora haremos lo mismo con UnlimitedCustomer. Crearemos la clase y le configuraremos en el constructor su tipo.

public class  UnlimitedCustomer: Customer{
    public UnlimitedCustomer(){
        Type = CustomerType.Unlimited;
    }
}

Tenemos nuestras 2 clases nuevas heredadas de customers. Ahora lo que debemos hacer es implementar nuestra clase GenerateStatement a cada una de las clases que corresponda perteneciendo cada lógica a una clase. Crearemos una clase Abstracta en Customer.

public abstract class Customer{
    public CustomerType Type { get; set; }
    public abstract MonthlyStatement GenerateStatement(MonthlyUsage monthlyUsage);
}

En sus hijos crearemos el método, sobreescribiendo, con la implementación de cada uno en particular.

public class  PayAsYouGoCustomer: Customer{
    public PayAsYouGoCustomer(){
        Type = CustomerType.PayAsYouGo;
    }
    public override MonthlyStatement GenerateStatement(MonthlyUsage monthlyUsage){
        var statement = new MonthlyStatement();
        stament.CallCost = 0.11f * usage.CallMinutes;
        stament.SmsCost = 0.13f * usage.SmsCount;
        stament.TotalCost = stament.CallCost + stament.SmsCost;

        return statement;
    }
}
public class  UnlimitedCustomer: Customer{
    public UnlimitedCustomer(){
        Type = CustomerType.Unlimited;
    }
    public override MonthlyStatement GenerateStatement(MonthlyUsage monthlyUsage){
       var statement = new MonthlyStatement();
       stament.TotalCost = 45.90f;

       return statement;
    }
}

Ahora solo nos queda remover los constructores. Realmente no los necesitamos, también, debemos removerlo de nuestra clase Abstracta Customer como también las enumeraciones.

public class  PayAsYouGoCustomer: Customer{
    public override MonthlyStatement GenerateStatement(MonthlyUsage monthlyUsage){
        var statement = new MonthlyStatement();
        stament.CallCost = 0.11f * usage.CallMinutes;
        stament.SmsCost = 0.13f * usage.SmsCount;
        stament.TotalCost = stament.CallCost + stament.SmsCost;

        return statement;
    }
}
public class  UnlimitedCustomer: Customer{
    public override MonthlyStatement GenerateStatement(MonthlyUsage monthlyUsage){
       var statement = new MonthlyStatement();
       stament.TotalCost = 45.90f;

       return statement;
    }
}

public abstract class Customer{    
    public abstract MonthlyStatement GenerateStatement(MonthlyUsage monthlyUsage);
}

Conclusión

Por medio del polimorfismo vimos cómo podemos deshacernos de un Switch que está basado en un tipificación de objetos por medio de una propiedad. Movimos los métodos a las clases correctas, y por último factorizamos toda nuestra lógica.

El el próximo post veremos codigo duplicados!.

Fernando Sonego

Deja un comentario

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