La orientación a objetos es una palabra con gancho. Llamar a cualquier cosa "orientada a objetos" puede hacerla parecer más elegante. Ruby reclama ser un lenguaje de guiones orientado a objetos: pero, ¿Qué significa exactamente "orientado a objetos"?
Existe una gran variedad de respuestas a esta pregunta, y probablemente todas ellas se pueden reducir a la misma cosa. En vez de recapitular demasiado deprisa, pensemos un momento en el paradigma de la programación tradicional.
Tradicionalmente, un problema informático se ataca produciendo algún tipo de representación de datos y procedimientos que operan sobre esos datos. Bajo este modelo, los datos son inertes, pasivos e incapaces. Están a la completa merced de un gran cuerpo procedimental, que es activo, lógico y todopoderoso.
El problema con esta aproximación es, que los programas los escriben programadores, que son humanos, que sólo pueden retener cierto número de detalles en sus cabezas en un momento determinado. A medida que crece el proyecto, el núcleo procedimental crece hasta un punto que se hace difícil recordar cómo funciona todo el conjunto. Pequeños lapsos de pensamiento o errores tipográficos llegan a ser errores muy ocultos. Empiezan a surgir interacciones complejas e inintencionadas dentro de este núcleo y el mantenimiento se convierte en algo parecido a transportar un calamar gigante intentado que ninguno de sus tentáculos te alcance la cara. Existen políticas de programación que ayudan a minimizar y localizar errores dentro de este paradigma tradicional pero existe una solución mejor que pasa fundamentalmente por cambiar la forma de trabajar.
Lo que hace la programación orientada a objetos es, delegar la mayoría del trabajo mundano y repetitivo a los propios datos; modifica el concepto de los datos que pasan de pasivos a activos. Dicho de otra forma.
Dejamos de tratar cada pieza de dato como una caja en la que se puede abrir su tapa y arrojar cosas en ella.
Empezamos a tratar cada pieza de dato como una máquina funcional cerrada con unos pocos interruptores y diales bien definidos.
Se podría pensar que estamos haciendo más trabajo nosotros mismos, pero esta forma de trabajo tiende a ser un buen método para evitar que vayan mal todo tipo de cosas.
Comencemos con un ejemplo que es demasiado simple para tener algún valor práctico pero que al menos muestra parte del concepto. Nuestro coche consta de un odómetro. Su trabajo consiste en llevar un registro de la distancia recorrida desde la última vez que se pulsó el botón de reinicialización. ¿Cómo podríamos representar esto en un lenguaje de programación? En C, el odómetro sería, simplemente, una variable numérica de tipo float. El programa manipularía esa variable aumentando el valor en pequeños incrementos y ocasionalmente la reinicializaría a cero cuando fuese apropiado. ¿Qué hay de malo en esto? Un error en el programa podría asignar un valor falso a la variable, por cualquier número de razones inesperadas. Cualquiera que haya programado en C sabe que se pueden perder horas o días tratando de encontrar ese error que una vez encontrado parece absurdamente simple. (El momento de encontrar el error es indicado por una sonora palmada en la frente).
En un contexto orientado a objetos, el mismo problema se puede atacar desde un ángulo completamente diferente. La primera cosa que se pregunta un programador al diseñar el odómetro no es "¿qué tipos de datos son los más cercanos para representar esta cosa?" sino "¿cómo se supone que actúa esta cosa?". La diferencia termina siendo profunda. Es necesario dedicar cierto tiempo a decidir para qué es exactamente un odómetro y cómo se espera que el mundo exterior interactúe con el. Se decide entonces construir una pequeña máquina con controles que permitan incrementar, reinicializar y leer su valor y nada más.
El odómetro se crea sin un mecanismo para asignarle un valor arbitrario, ¿Por qué? porque es de todos sabido que los odómetros no trabajan de esa forma. Existen sólo unas cuantas cosas que un odómetro puede hacer, y sólo permitimos esas cosas. Así, si alguien desde un programa trata de asignar algún otro valor (por ejemplo, la temperatura límite del sistema de control de climatización del vehículo) al odómetro, aparece de inmediato una indicación de lo que va mal. Al ejecutar el programa se nos dice (o posiblemente, al compilarlo dependiendo de la naturaleza del lenguaje) que No se nos permite asignar valores arbitrarios al objeto Odómetro. El mensaje podría ser menos preciso, pero sí razonablemente próximo al problema. Esto no evita el error, ¿verdad? pero apunta rápidamente en la dirección de la causa. Esta es sólo alguna de múltiples formas en las que la programación OO nos puede evitar muchas pérdidas de tiempo.
Existe, normalmente, un nivel de abstracción superior a éste porque resulta que es igual de fácil construir una factoría que hace máquinas como hacer una máquina individual. Es poco probable que construyamos un único odómetro, sino que nos preparamos para construir cualquier cantidad de odómetros a partir de un único patrón. El patrón (o si los prefieres, la factoría de odómetros) es lo que se conoce como clase y el odómetro individual sacado del patrón (o construido en la factoría) se conoce como objeto. La mayoría de los lenguajes OO necesitan una clase para tener un nuevo tipo de objeto pero Ruby no.
Conviene resaltar aquí que la utilización de un lenguaje OO no obliga a un diseño OO válido. Es posible, en cualquier lenguaje, escribir código poco claro, descuidado, mal concebido, erróneo e inestable. Lo que permite Ruby (en oposición, especialmente, a C++) es que la práctica de la programación OO sea lo suficientemente natural para que, incluso, trabajando a pequeña escala no se sienta la necesidad de recurrir a un código mal estructurado por evitar esfuerzo. Se tratará la forma en que Ruby logra este admirable propósito a medida que avancemos en esta guía; el próximo tema serán los "interruptores y diales" (métodos del objeto) y a partir de aquí pasaremos a las "factorías" (clases). ¿Sigues con nosotros?