Orientación a objetos y la importancia del «Tell, Don’t Ask»

5
13185

Actualizaciones:

  • 23-07-2012: se aclará la definición de polimorfismo para que no lleve a confusión.

 

Me he animado a escribir este artículo porque hace poco leí un estupendo artículo (William Cook’s Fusings: A Proposal for Simplified, Modern Definitions of «Object» and «Object Oriented») donde el autor propone una renovada y simplificada definición de lo que se entiende por ‘Orientación a Objetos’, y no podía estar más de acuerdo con él.

Así William Cook propone la siguiente definición: An object is a first-class, dynamically dispatched behavior. Lo que podríamos traducir como: Un objeto es un comportamiento de primera-clase, servido dinámicamente.

Aunque animo encarecidamente a leer el post de William Cook para una explicación más detallada, la explicación corta podría ser:

  • un comportamiento – un objeto es una abstracción de un comportamiento, de forma que un objeto tiene sentido por las cosas que es capaz de hacer (en última instancia por sus métodos públicos).
  • de primera-clase – con esto se refiere a que un objeto puede usarse en cualquier sitio donde se puede usar un valor, como asignarse a una variable, pasarse como parámetro a una función, …
  • servido dinámicamente – es lo que llamaríamos normalmente polimorfismo. Se refiere a que si hacemos una petición a un objeto, no seremos capaces de determinar en tiempo de compilación (estáticamente) la operación que se va a ejecutar, y sólo podremos determinar esta en tiempo de ejecución (dinámicamente).

 

Aquí quiero resaltar especialmente el punto del polimorfismo ya que considero que es la característica que realmente da potencia a un lenguaje Orientado a Objetos y hace que sea útil (sin esta característica, la Orientación a Objetos no serviría ni para envolver pescado).

El polimorfismo nos permite enviar un mensaje a distintos objetos del mismo tipo y que estos respondan de distinta manera. Esto nos permite trabajar con objetos sin conocer su tipo concreto, simplemente su contrato. De esta forma nosotros llamaremos a sus métodos públicos (su contrato) y en tiempo de ejecución se determinará el comportamiento que se va a ejecutar.

El ejemplo típico es el de las figuras, donde yo puedo tener una colección de objetos figura, y recorrer esa colección llamando al método ‘dibujar’ de cada objeto; viendo como en cada caso particular se dibujará un círculo, un cuadrado, un triángulo, … En tiempo de compilación (estáticamente) no podemos determinar que se va a dibujar, ya que no conocemos ni los objetos ni el orden de estos dentro de la colección, y sólo en ejecución (dinámicamente) veremos el comportamiento de cada objeto ante el mensaje ‘dibujar’.

Esto ya empieza a ser interesante ya que nos permite cumplir el principio Open/Close, por el cual un sistema debe estar abierto a ampliaciones y cerrado a modificaciones. Esto quiere decir que si yo quiero añadir funcionalidad a un sistema debe ser tan fácil como añadir piezas nuevas, pero en ningún caso modificar las existentes. En el caso de las figuras vemos como podemos añadir nuevos tipos de figuras con nuevos comportamientos ante el mensaje ‘dibujar’.

Aun así la potencia del polimorfismo la exprimimos cuando lo combinamos con la inversión de control o inversión de dependencias, ya que esta inversión permite que las abstracciones trabajen con independencia de las implementaciones concretas, rompiendo así el acoplamiento entre lo abstracto y lo concreto, lo que nos va a permitir posponer y cambiar decisiones de arquitectura tantas veces como queramos.

Inversión de dependencias

El ejemplo típico en este caso sería cuando nuestra lógica de negocio se tiene que conectar a una base de datos para guardar la información. No puedo cometer el error de que mi lógica de negocio dependa de una implementación concreta de base de datos ya que esto limitará mis opciones a la hora de hacer el despliegue en producción, podría incluso limitar mis clientes si estos no ‘comulgan’ con la base de datos de mi elección.

Así que lo mejor es invertir el control de forma que mi lógica de negocio no dependa directamente del objeto de base de datos, sino que un tercero lo crea y se lo pasa a la lógica de negocio para que esta lo utilice. En este punto hemos roto el acoplamiento entre la lógica de negocio y el sistema de persistencia, de forma que podemos cambiar la implementación de este último sin alterar el primero. Así podemos, por ejemplo simular este comportamiento para hacer que nuestros test se ejecuten mucho más rápido ya que no se conectan realmente a una base de datos y se están ejecutando en memoria. Incluso si posponemos la decisión de la elección del motor de base de datos utilizando una implementación en memoria podemos llegar a darnos cuenta de que ni siquiera necesitamos una base de datos.

Con esta rotura de acoplamiento es como conseguimos que nuestro sistema sea mucho más mantenible, ya que nos permite cambiar piezas concretas sin tener que cambiar código en el resto del sistema. Incluso nos hace más competitivos, ya que para clientes y entornos distintos podemos tener implementaciones distintas.

 

Pero ¿no habías dicho que ibas a hablar del «Tell, Don’t ask»? Pues si, pero creo que sin decirlo ya lo he dicho todo  😉

El «Tell, Don’t ask» es un meme que nos recuerda que no tenemos que usar los objetos para pedirles cosas y según la información que nos devuelven tomar decisiones, sino que lo que debemos hacer es decirles a los objetos que hagan cosas y estos objetos internamente tomaran sus propias decisiones según de su estado.

Este meme casa perfectamente con lo que veíamos en la definición inicial y la idea de que un objeto se define por su comportamiento; y además es fundamental para sacar partido al polimorfismo y la inversión de control. Es decir si nos dedicamos a preguntar y tomar nosotros las decisiones estaremos acoplándonos, será prácticamente imposible dar el cambiazo al objeto, estaremos incumpliendo el principio Open/Close, nuestro sistema será más difícil de mantener, y por lo tato más caro.

 

Se que en este pequeño artículo hemos visto muchos conceptos y de hecho tendríamos para hablar durante días, pero os animo a que sigáis investigando en esta línea ya que conseguiréis potenciar enormemente vuestros diseños. Al fin y al cabo recordar que usar un lenguaje Orientado a Objetos no quiere decir que estéis haciendo Orientación a Objetos 😉

Y por cierto, aquello que nos querían vender al principio sobre que la Orientación a Objetos es genial porque nos permite modelar objetos del mundo real y nos permite hacer objetos reutilizables entre sistemas, siento decir que no son más que paparruchadas!!!

 

 

5 COMENTARIOS

  1. Conocía este meme como dices, pero hoy me queda mas claro y digo claro porque el mas que usar clases y objetos es pensar en objeto… Muchas Gracias por este tutorial

    • No entiendo muy bien la pregunta pero te explico un poco el ciclo.

      Un objeto para poder usarlo siempre lo tienes que crear primero. La idea es que un objeto realiza una serie de funciones (sabe hacer una cosa). Cuando desde otro sitio quieres utilizar esas funciones, necesitas una referencia a ese objeto y llamarlo para que él haga su trabajo. (eso es el «Tell, Don’t ask»)

      Imagina que tienes un objeto de carrito de la compra, que contiene todos los artículos que ha comprado un cliente. Si ahora quieres saber el total de la compra podrías pedirle al carrito la lista de artículos, iterar por ella, e ir sumando los importes. Esto viola el Tell, Don’t ask. Para hacerlo bien lo correcto sería pedirle al carrito de la compra el total de la compra, ya que es el carrito quién tiene los artículos (es decir no le estamos preguntando al carrito que artículos tiene, sino que le estamos diciendo que nos haga él el cálculo del total).

      Cuando antes decía que «necesitas una referencia a ese objeto», aquí es donde tú puedes crear la instancia del objeto o donde te la pueden pasar de alguna manera (en el constructor, en un método, incluso en un service locator). Aquí es donde conseguimos la «inversión de control», ya que, como no conoces el origen del objeto, gracias al polimorfismo te podrían pasar otra implementación y tú código sería el mismo, no tendrías ni que cambiarlo ni recompilarlo ni hacer nada.

      Espero que esto te aclare un poco.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad