martes, 9 de mayo de 2017

Historia de una conversa sobre OOP

Y luego de un largo rato volvemos por acá con una historia basada en hechos reales, sobre una conversación muy didáctica que tuve con un amigo:

-Mr. Bladi, he escuchado que Ud. se opone, es más desprecia, al uso de las clases Utilitarias y los métodos estáticos en la programación orientada a objetos (OOP).
-Ciertamente. No aconsejo su uso y lo considero una mala práctica, algo ofensivo en la OOP.
-Pero si no podemos usar clases utilitarias y métodos estáticos, ¿Cuáles opciones Tenemos?
-Tenemos unas cuantas opciones. Pero antes un poco de contexto. Las clases utilitarias, esas que están repletas de métodos estáticos, no son objetos reales, son un recurso totalmente procedimental y rompen el principio de encapsulamiento en la OOP.
Peor aún, las clases utilitarias con su torrente de métodos estáticos, son muy difíciles de testear en las pruebas automáticas. 

TODO MÉTODO ESTÁTICO, E INCLUSO LOS PRIVADOS, SON CANDIDATOS PARA UNA NUEVA CLASE, es decir son elegibles para una nueva abstracción.




Veamos un ejemplo…. Y como siempre nuestro grito de guerra: Manos a las sobras!!!
Supongamos que tenemos la usual clase:

public class MyMath {
 public static double sumar(double a, double b) {
  return a + b;
 }

 public static double restar(double a, double b) {
  return a - b;
 }

 public static double multiplicar(double a, double b) {
  return a * b;
 }

 public static double dividir(double a, double b) {
  return a / b;
 }
}


La cual podemos y solemos utilizar del siguiente modo:

double result = MyMath.sumar(2.0, 3.0);
System.out.println(“result = “ + result);
System.out.println(“result * 3 + 2 = ” + (MyMath.sumar(MyMath.multiplicar(result, 3.0),2.0)));

Primera implementación:

Haciendo un análisis podemos observar que tenemos una operación entre dos operadores numéricos, lo que se puede representar en una interface tal como:
public interface Operacion{
    public double ejecutar(double ope1, double ope2);
}

Escribimos clases para cada implementación de Operacion que deseemos:

public final class Sumar implements Operacion {
        public double ejecutar(double ope1, double ope2){
            return ope1 + ope2;
        }
}
public final class Multiplicar implements Operacion {
        public double ejecutar(double ope1, double ope2){
            return ope1 * ope2;
        }
}

Y la podemos usar de la siguiente forma:

Operacion sumar = new Sumar();
Operacion multiplicar = new Multiplicar();
double result = sumar.ejecutar(2.0, 3.0);
System.out.println(“result = “ + result);
System.out.println(“result * 3 + 2 = ” + (sumar.ejecutar(multiplicar.ejecutar(result, 3.0),2.0))); 

Como ven, nada de métodos estáticos y dejamos que los objetos trabajen para nosotros. Pero.... siempre hay un pero, esta implementación tampoco es del todo correcta. Según los principios pigbar, no deben existir clases con sólo métodos, así como no deben existir clases con sólo propiedades. Ambos casos son síntomas de un mal diseño.

Así que mejoremos este enfoque.

Segunda implementación: 

 Ahora la interface Operacion queda de la siguiente forma:

public interface Operacion{
    public double ejecutar();
}

Mucho mas sencilla. Y ahora sus implementaciones:

public final class Sumar implements Operacion {
    private double ope1, ope2;
    public Sumar(double ope1, double ope2){
        this.ope1= ope1;
        this.ope2= ope2;
    }
    public double ejecutar(){
        return ope1 + ope2;
    }
}
public final class Multiplicar implements Operacion {
    private double ope1, ope2;
    public Multiplicar(double ope1, double ope2){
        this.ope1= ope1;
        this.ope2= ope2;
    }
    public double ejecutar(){
        return ope1 * ope2;
    }
}

Como ven, muy parecidas pero con grandes diferencias. Estos si son verdaderos objetos encapsulando comportamiento y datos. Ahora los podemos usar de la siguiente forma:

Operacion sumar = new Sumar(2.0, 3.0);
double result = sumar.ejecutar();
System.out.println(“result = “ + result);
System.out.println(“result * 3 + 2 = ” + (new Sumar(new Multiplicar(result, 3.0).ejecutar(),2.0).ejecutar())); 

Hemos dado un paso al frente en nuestra visión de la programación orientada a objetos. Pero aún lo podemos hacer de forma distinta...

Tercera implementación:


Y… ¿Qué tal si hacemos esto más al estilo “Fluent”? Pues veamos…. Vamos a crear una Interface Operable:

public interface Operable{
    public double valor();
    public Operable sumar(double operando);
    public Operable multiplicar(double operando);
}

E implementamos la Interface:

public final class Operando implements Operable{
    private double valor;
    public Operando(){
        this(0.0);
    }
    public Operando(double number){
        valor= number;
    }
    public double valor(){
        return valor;
    }
    public Operable sumar(double operando){
        return new Operando(this.valor + operando);
    }
    public Operable multiplicar(double operando) ){
        return new Operando(this.valor * operando);
    }
    @overwrite
    public String toString(){
        return “” + valor;
    }
}

Ahora lo podemos usar escribiendo código como el siguiente:

Operando result = new Operando(2.0).sumar(3.0);
System.out.println(“result = “ + result);
System.out.println(“result * 3 + 2 = ” + result.multiplicar(3.0).sumar(2.0));

Elegante, ¿no les parece? Así que nada de excusas para clases utilitarias y métodos estáticos.

-Mr. Bladi, cuando crezca quiero ser como Ud.
-Lo serás mi pequeño saltamontes. Lo serás...

2 comentarios:

  1. "una operación entre dos operadores" esto es = a tres ?

    ResponderEliminar
    Respuestas
    1. Si, "=" es una operación, y los valores a su izquierda o derecha serían los operandos u operadores.

      Eliminar