sábado, 20 de mayo de 2017

Getters y Setters grandes enemigos de la OOP

Es mucho el artículo, publicación y blogs de , ¿supuestos?, entendidos en el tema que hablan sobre, o exponen el problema con, el uso o más bien ABUSO de los Getters y Setters en la programación orientada a objetos (OOP).

Es este un tema recurrente y para nada nuevo, no señor, de hecho viene dando vueltas en el entorno de desarrollos OOP desde hace un buen rato. Pero....

¿Cuál es el gran problema con usar getters y setters?


Interesante pregunta...

En principio no parece haber nada malo con ellos, los getters y setters, y muchas herramientas y frameworks promueven su uso, es más, existen cientos y miles de programas construidos con estos elementos y funcionan bastante bien.

Sí funcionan ¿porqué cambiarlos?.

¿Que hace que cada cierto tiempo salga algún papanatas con ínfulas de sabio, como el autor, diciendo que el uso de estos recursos de programación debe ser evitado, abolido e incluso castigado!!!?

Algo debe estar sucediendo para que se pretenda imponer reglas que eliminan de facto a estas formas de construcción.

Intentaré responder a estas cuestiones de la mejor manera.

Vamos con un ejemplo...

Me gusta ver televisión a un volumen que se podría considerar alto, al menos para mi señora lo es, por lo cual frecuentemente me encuentro con la tarea de tener que ajustar el nivel del audio al volumen deseado. Esto último lo realizo o bien pulsando un botón o moviendo una perilla hasta que consigo el resultado esperado.

Así de simple. Pulso un botón dispuesto para tal fin, o muevo un control con igual propósito. Recibo por lo general un feedback del nivel de volumen alcanzado tanto en forma numérica como el que llega a mis oídos.  Y ya.

No soy técnico en electrónica, ni tengo que serlo para operar un control remoto de un televisor u otro aparato cualquiera.

Se imagina que para lograr usar el control remoto y subir o bajar el volumen de la Tv tuviera que hacer algo como lo siguiente:

  • Obtener tarjeta de circuito electrónico.
  • Buscar chip de volumen.
  • Buscar condensador de potencia.
  • Almacenar carga.
  • Poner carga en circuito... 

y listo... volumen modificado!!!

¿Capta la diferencia?

En el primer caso usamos el control remoto mediante el "Contrato" o "Interface" que el mismo nos expone para poder operar sobre el. Sin preocuparnos de cómo está construido ni cual es su estructura interna.

En el segundo caso debemos ser consientes de cómo demonios está construido el control remoto, es más, debemos saber como funciona internamente, para poder lograr  modificar el volumen del Tv.

Ambos logran el mismo objetivo. Intrínsecamente y técnicamente hablando ninguna forma es incorrecta. El problema es que una de esas formas es totalmente insegura e inmantenible mientras que la otra es sencilla y protegida. Ud decide cual es cual.

Y es esta última forma de trabajo la que promueven y permiten los getters y setters. Por eso son tan dañinos. Así de simple. Permiten hacer una Vivisección del objeto al que pertenecen, descubriendo sus componentes y estructuras internas y facilitando su modificación, alteración y reemplazo, lo cual no siempre resulta deseable, de hecho pocas veces lo es, y en general termina siendo una practica contraproducente.

En vez de programar para una Abstracción, Contrato o Interface, los programadores actuales terminan PIDIENDO un montón de datos o estructuras a los objetos para ellos, los programadores, hacer el trabajo y modificar o actualizar  el estado interno del objeto. Trabajan exactamente como se describe en el segundo ejemplo.

Uno de estos supuestos desarrolladores orientados a objetos hacen un diseño o construcción como la siguiente:

    public classs ControlRemoto{
        private Integer volumen;
        public Integer getVolumen(){
            return volumen;
        }
        public void setVolumen(Integer volumenNuevo){
            volumen= volumenNuevo;
        }
    }

    public class VolumenUtil{
        public static void subirVolumen(ControlRemoto control){
            control.setVolumen(control.getVolumen() + 1);
        }
        public static void bajarVolumen(ControlRemoto control){
            control.setVolumen(control.getVolumen() - 1);
        }
    }

    public class Tester{
        public static void main(String[] args){
            ControlRemoto myControl = new ControlRemoto();
            myControl.setVolumen(40);
            System.out.println("Volumen actual : " + myControl.getVolumen());
            VolumenUtil.subirVolumen(myControl);
            VolumenUtil.subirVolumen(myControl);
            VolumenUtil.bajarVolumen(myControl);
            System.out.println("Volumen  final : " + myControl.getVolumen());
        }
    }

Admítalo, Ud ha visto, y seguramente hasta ha escrito, código como ese. Usando clases "utilitarias" y pidiéndole datos al objeto para Ud hacer el trabajo. Para Ud estar bajo control.

¿Ve como se expone la construcción interna del objeto?
¿Ve como desde afuera se puede cambiar el estado del objeto ControlRemoto desde cualquier parte del sistema?

Esto implica que, en un ambiente multihilos y distribuido, nunca podríamos confiar en los objetos ControlRemoto, por que cualquiera, desde cualquier parte del programa, podría cambiar su estado en forma arbitraria, sin que los objetos ControlRemoto tengan control ni conocimiento sobre ello, y eso es FATAL. Es BASURA. Eso es peor que la programación procedimental.

Digamos ahora que Ud Cree haber entendido el problema y propone, inocentemente, el código siguiente como alternativa o mejor solución al anterior:



    public classs ControlRemoto{
        private Integer volumen;

        public ControlRemoto(Integer volumen){
            this.volumen = volumen;
        }
        public Integer subirVolumen(){
            return ++volumen;
        }
        public Integer bajarVolumen(){
            return --volumen;
        }
        public Integer getVolumen(){
            return volumen;
        }
    }

    public class Tester{
        public static void main(String[] args){
            ControlRemoto myControl = new ControlRemoto(40);
            System.out.println("Volumen actual : " + myControl.getVolumen());
            myControl.subirVolumen();
            myControl.subirVolumen();
            myControl.bajarVolumen();
            System.out.println("Volumen  final : " + myControl.getVolumen());
        }
    }

Y esta ya es una gran mejora en verdad sobre la propuesta anterior. No obstante, imaginemos que pasaría si en la clase Tester hiciéramos  algo como lo siguiente:

    myControl.getVolumen() = 50;
    System.out.println("Volumen actual : " + myControl.getVolumen());

¿Cuál sería el resultado impreso? ¿Se alteró el valor de la propiedad "volumen"?
¿Cómo evitamos este efecto no deseado?
Es por ello que los objetos deben ser lo mayormente inmutables que sea posible.
Internamente necesito que la propiedad volumen pueda ser modificada. Pero es mi deber garantizar que desde afuera de la clase no puedan cambiarla o alterarla.

Propongamos entonces una nueva versión de la clase CotrolRemoto:

    public classs ControlRemoto{
        private Integer volumen;

        public ControlRemoto(Integer volumen){
            this.volumen = volumen;
        }
        public Integer subirVolumen(){
            return ++volumen.clone();
        }
        public Integer bajarVolumen(){
            return --volumen.clone();
        }
        public Integer getVolumen(){
            return volumen.clone();
        }
    }

    public class Tester{
        public static void main(String[] args){
            ControlRemoto myControl = new ControlRemoto(40);
            System.out.println("Volumen actual : " + myControl.getVolumen());
            myControl.subirVolumen();
            myControl.subirVolumen();
            myControl.bajarVolumen();
            myControl.getVolumen() = 50;
            System.out.println("Volumen  final : " + myControl.getVolumen());
        }
    }

Las pruebas se las dejo en manos de Uds.

Los getter y setter, su uso y abuso, son los que han provocado comentarios como el de quien dijo que:

"Con la programación orientada a objetos se ha conseguido es un desastre, queríamos una Banana, pero lo que obtuvimos fue un Gorila sosteniendo una Banana y la Jungla ENTERA!!!".

Recuerde, dígale al objeto QUE hacer y dele los elementos para hacerlo. NO le pida datos al objeto para Ud hacer el trabajo. Sea flojo, mandon e inteligente al mismo tiempo. Deje que los objetos trabajen para Ud.

Espero sus comentarios y opiniones.

No hay comentarios:

Publicar un comentario