Nota: Este post ha sido importado de mi blog de geeks.ms. Es posible que algo no se vea del todo "correctamente". En cualquier caso puedes acceder a la versión original aquí
Ayer tuve el placer de participar en un hangout de #JsIO junto a dos bestias pardas como Erick Ruiz y Tomás Corral discutiendo si JavaScript es o no es un lenguaje orientado a objetos.
Así que aprovecho para escribir este post y hacer algunas reflexiones más al respecto sin las prisas ni la improvisación del “directo”.
¿Qué es la orientación a objetos?
Es complicado definir que significa exactamente “orientación a objetos”. Seguro que si le preguntas a varia gente distinta obtendrás diferentes respuestas. Con puntos coincidentes, pero también puntos divergentes. Esto se debe a que el concepto es lo suficientemente vago como para admitir varias interpretaciones y además de que la gran mayoría de lenguajes no son 100% orientados a objetos.
Si yo hubiese de definir orientación a objetos diría básicamente que se trata de modelar el problema, y por lo tanto de diseñar un programa, basándose en la interacción entre distintos objetos que tienen determinadas propiedades y con los cuales se pueden realizar determinadas acciones. Los objetos tienen tres propiedades fundamentales:
- Comportamiento: Se define como “lo que es posible hacer con un objeto”. En terminología más purista, el conjunto de mensajes a los que este objeto responde (en OOP purista se usa la terminología mensaje para referirse al paso de información entre objetos).
- Estado: Los objetos tienen un estado en todo momento, definido por el conjunto de las variables o campos que contienen. El estado es pues la información contenida en un objeto.
- Identidad: Cada objeto existe independientemente del resto. Pueden haber dos objetos iguales pero no tienen porque ser el mismo (de igual forma que dos mellizos pueden ser idénticos, pero no por ello dejan de ser dos personas).
No iría mucho más allá, porque para mi esta definición contiene la clave que diferencia de la POO de la programación procedural: Los datos (estado) y funciones relacionadas (comportamiento) están agrupados en una sola entidad (objeto), en lugar de estar dispersos en el código. Es decir, el objeto encapsula los datos y el comportamiento. Como desarrollador debemos pensar en modelar nuestro sistema como un conjunto de objetos, en lugar de como un conjunto de funciones (procedimientos) invocadas una tras otra y pasándose datos más o menos arbitrarios para solucionar el problema.
El resto son detalles: herencia, polimorfismo, visibilidad… son detalles. No son menores, ciertamente, pero desde un punto de visto teórico, son meros detalles.
Y fíjate: he sido capaz de definir POO sin usar la palabra clase una sola vez. Y es que las clases son un mecanismo para implementar la POO, pero nada más. Y por supuesto, no son el único.
JavaScript tiene objetos, y estos objetos tienen comportamiento, estado e identidad. Así que desde punto de vista… Es orientado a objetos.
Pero como entiendo que esta respuesta puede no colmar a todos, especialmente a aquellos que vengan de Java, C# o algún otro lenguaje orientado a objetos “tradicional” (debería decir basado en clases) vayamos a ver como en JavaScript también tenemos esos detalles que mencionaba antes disponibles… 😉
¿Herencia en JavaScript?
Vale… Antes que nada: Si has aprendido POO con Java, C#, C++ o algún otro lenguaje basado en clases recuerda el concepto fundamental: No hay clases en JavaScript. Por lo tanto, hazte un favor y deja de pensar en clases cuando desarrolles en JavaScript. Te ahorrarás muchos dolores de cabeza.
La herencia en JavaScript es herencia entre objetos.
-
En un lenguaje basado en clases, la herencia es una relación definida entre clases. Si una clase Rectángulo deriva de la clase Figura, todos los objetos Rectángulo heredarán todo el comportamiento y estado definidos en la clase Figura.
-
En JavaScript la herencia es una relación definida entre objetos. Si un objeto deriva de otro, heredará todo el comportamiento y estado definido en el objeto base.
El mecanismo de herencia que se usa en JavaScript se conoce como herencia por prototipo y para resumirlo diré básicamente que cada objeto tiene asociado un prototipo. Si el desarrollador invoca un método sobre un objeto y este método NO está definido en el propio objeto, el motor de JavaScript buscará el método en el prototipo de este objeto. Por supuesto el prototipo es un objeto y puede tener otro prototipo y así sucesivamente, creando lo que se conoce como cadena de prototipado. La pregunta es pues como crear y asignar el prototipo de un objeto. Hay varias maneras de hacerlo, pero veamos una rápida y sencilla:
<div style="background: #ddd; max-height: 300px; overflow: auto">
<ol start="1" style="background: #1e1e1e; margin: 0 0 0 2em; padding: 0 0 0 5px; white-space: nowrap">
<li>
<span style="background:#1e1e1e;color:#569cd6">var</span><span style="background:#1e1e1e;color:#dcdcdc"> figura </span><span style="background:#1e1e1e;color:#b4b4b4">=</span><span style="background:#1e1e1e;color:#dcdcdc"> {</span>
</li>
<li>
<span style="background:#1e1e1e;color:#dcdcdc"> </span><span style="background:#1e1e1e;color:#dcdcdc">draw</span><span style="background:#1e1e1e;color:#b4b4b4">:</span><span style="background:#1e1e1e;color:#dcdcdc"> </span><span style="background:#1e1e1e;color:#569cd6">function</span><span style="background:#1e1e1e;color:#b4b4b4">()</span><span style="background:#1e1e1e;color:#dcdcdc"> {</span>
</li>
<li>
<span style="background:#1e1e1e;color:#dcdcdc"> </span><span style="background:#1e1e1e;color:#dcdcdc">console</span><span style="background:#1e1e1e;color:#b4b4b4">.</span><span style="background:#1e1e1e;color:#dcdcdc">log</span><span style="background:#1e1e1e;color:#b4b4b4">(</span><span style="background:#1e1e1e;color:#d69d85">"figura::draw"</span><span style="background:#1e1e1e;color:#b4b4b4">);</span>
</li>
<li>
<span style="background:#1e1e1e;color:#dcdcdc"> }</span>
</li>
<li>
<span style="background:#1e1e1e;color:#dcdcdc">}</span><span style="background:#1e1e1e;color:#b4b4b4">;</span>
</li>
<li>
<span style="background:#1e1e1e;color:#569cd6">var</span><span style="background:#1e1e1e;color:#dcdcdc"> rect </span><span style="background:#1e1e1e;color:#b4b4b4">=</span><span style="background:#1e1e1e;color:#dcdcdc"> Object</span><span style="background:#1e1e1e;color:#b4b4b4">.</span><span style="background:#1e1e1e;color:#dcdcdc">create</span><span style="background:#1e1e1e;color:#b4b4b4">(</span><span style="background:#1e1e1e;color:#dcdcdc">figura</span><span style="background:#1e1e1e;color:#b4b4b4">);</span>
</li>
<li>
<span style="background:#1e1e1e;color:#dcdcdc">rect</span><span style="background:#1e1e1e;color:#b4b4b4">.</span><span style="background:#1e1e1e;color:#dcdcdc">draw</span><span style="background:#1e1e1e;color:#b4b4b4">();</span>
</li>
</ol>
</div></p>
Si ejecutas este código, la salida por la consola es “figura::draw”. La clave es el método Object.create. Est
e método crea un objeto vacío cuyo prototipo es el objeto pasado por parámetro. Esta es una forma sencilla de tener herencia en JavaScript.
Igual te preguntas si el objeto rect puede sobreescribir la implementación de draw (si vienes de Java sabrás que una clase hija puede redefinir los métodos de su clase base; si vienes de C# sabrás eso mismo siempre que los métodos sean virtuales). La respuesta es claro que sí:
<div style="background: #ddd; max-height: 300px; overflow: auto">
<ol start="1" style="background: #1e1e1e; margin: 0 0 0 2.5em; padding: 0 0 0 5px; white-space: nowrap">
<li>
<span style="background:#1e1e1e;color:#569cd6">var</span><span style="background:#1e1e1e;color:#dcdcdc"> figura </span><span style="background:#1e1e1e;color:#b4b4b4">=</span><span style="background:#1e1e1e;color:#dcdcdc"> {</span>
</li>
<li>
<span style="background:#1e1e1e;color:#dcdcdc"> </span><span style="background:#1e1e1e;color:#dcdcdc">draw</span><span style="background:#1e1e1e;color:#b4b4b4">:</span><span style="background:#1e1e1e;color:#dcdcdc"> </span><span style="background:#1e1e1e;color:#569cd6">function</span><span style="background:#1e1e1e;color:#b4b4b4">()</span><span style="background:#1e1e1e;color:#dcdcdc"> {</span>
</li>
<li>
<span style="background:#1e1e1e;color:#dcdcdc"> </span><span style="background:#1e1e1e;color:#dcdcdc">console</span><span style="background:#1e1e1e;color:#b4b4b4">.</span><span style="background:#1e1e1e;color:#dcdcdc">log</span><span style="background:#1e1e1e;color:#b4b4b4">(</span><span style="background:#1e1e1e;color:#d69d85">"figura::draw"</span><span style="background:#1e1e1e;color:#b4b4b4">);</span>
</li>
<li>
<span style="background:#1e1e1e;color:#dcdcdc"> }</span>
</li>
<li>
<span style="background:#1e1e1e;color:#dcdcdc">}</span><span style="background:#1e1e1e;color:#b4b4b4">;</span>
</li>
<li>
<span style="background:#1e1e1e;color:#569cd6">var</span><span style="background:#1e1e1e;color:#dcdcdc"> rect </span><span style="background:#1e1e1e;color:#b4b4b4">=</span><span style="background:#1e1e1e;color:#dcdcdc"> Object</span><span style="background:#1e1e1e;color:#b4b4b4">.</span><span style="background:#1e1e1e;color:#dcdcdc">create</span><span style="background:#1e1e1e;color:#b4b4b4">(</span><span style="background:#1e1e1e;color:#dcdcdc">figura</span><span style="background:#1e1e1e;color:#b4b4b4">);</span>
</li>
<li>
<span style="background:#1e1e1e;color:#dcdcdc">rect</span><span style="background:#1e1e1e;color:#b4b4b4">.</span><span style="background:#1e1e1e;color:#dcdcdc">draw </span><span style="background:#1e1e1e;color:#b4b4b4">=</span><span style="background:#1e1e1e;color:#dcdcdc"> </span><span style="background:#1e1e1e;color:#569cd6">function</span><span style="background:#1e1e1e;color:#b4b4b4">()</span><span style="background:#1e1e1e;color:#dcdcdc"> {</span>
</li>
<li>
<span style="background:#1e1e1e;color:#dcdcdc"> console</span><span style="background:#1e1e1e;color:#b4b4b4">.</span><span style="background:#1e1e1e;color:#dcdcdc">log</span><span style="background:#1e1e1e;color:#b4b4b4">(</span><span style="background:#1e1e1e;color:#d69d85">"rect::draw"</span><span style="background:#1e1e1e;color:#b4b4b4">);</span>
</li>
<li>
<span style="background:#1e1e1e;color:#dcdcdc">}</span><span style="background:#1e1e1e;color:#b4b4b4">;</span>
</li>
<li>
<span style="background:#1e1e1e;color:#dcdcdc">rect</span><span style="background:#1e1e1e;color:#b4b4b4">.</span><span style="background:#1e1e1e;color:#dcdcdc">draw</span><span style="background:#1e1e1e;color:#b4b4b4">();</span>
</li>
</ol>
</div></p>
Ahora la salida por pantalla es “rect::draw”. ¿Qué ha sucedido? Pues como hemos llamado al método draw del objeto rect y este ya lo tiene definido, JavaScript ya lo invoca y no lo busca por la cadena de prototipos.
¿Lo ves? Herencia sin clases. ¡Es posible! 😉
¿Polimorfismo en JavaScript?
Una de las preguntas que salieron en el hangout fue si había polimorfismo en JavaScript. Respondí diciendo que esta pregunta implica una forma de pensar “basada en clases” pero que sí que era posible simular el polimorfismo en JavaScript.
Pero es que una de las claves es que no es necesario el concepto de polimorfismo en JavaScript. Antes que nada aclaremos que es el polimorfismo: La posibilidad de enviar un mismo mensaje (es decir, de invocar a un método) a varios objetos de naturaleza homogénea (wikipedia). Me gusta especialmente esta definición porque define polimorfismo sin usar la palabra “clase”. Si habéis aprendido POO con un lenguaje basado en clases quizá tenéis una definición de polimorfismo redactada de forma ligeramente distinta, algo como: Que los objetos de una clase derivada pueden tratarse como objetos de la clase base, pero manteniendo su comportamiento distintivo.
Supón que estás en un lenguaje basado en clases, como C#, y tienes la clase Figura y su derivada la clase Rect. La clase Figura define un método virtual llamado Draw() que es redefinido en la clase Rect. El polimorfismo implica que puedo pasar un objeto de la clase Rect a un método que acepte como parámetro un objeto de la clase Figura. Y que si este método llama al método Draw del parámetro Figura que ha recibido… se ejecutará el método Draw de la clase Rect, porque aunque el parámetro es de tipo Figura, el objeto real es de tipo Rect.
¿Y en JavaScript? Veamos:
<div style="background: #ddd; max-height: 300px; overflow: auto">
<ol start="1" style="background: #1e1e1e; margin: 0 0 0 2.5em; padding: 0 0 0 5px; white-space: nowrap">
<li>
<span style="background:#1e1e1e;color:#569cd6">var</span><span style="background:#1e1e1e;color:#dcdcdc"> figura </span><span style="background:#1e1e1e;color:#b4b4b4">=</span><span style="background:#1e1e1e;color:#dcdcdc"> {</span>
</li>
<li>
<span style="background:#1e1e1e;color:#dcdcdc"> </span><span style="background:#1e1e1e;color:#dcdcdc">draw</span><span style="background:#1e1e1e;color:#b4b4b4">:</span><span style="background:#1e1e1e;color:#dcdcdc"> </span><span style="background:#1e1e1e;color:#569cd6">function</span><span style="background:#1e1e1e;color:#b4b4b4">()</span><span style="background:#1e1e1e;color:#dcdcdc"> {</span>
</li>
<li>
<span style="background:#1e1e1e;color:#dcdcdc"> </span><span style="background:#1e1e1e;color:#dcdcdc">console</span><span style="background:#1e1e1e;color:#b4b4b4">.</span><span style="background:#1e1e1e;color:#dcdcdc">log</span><span style="background:#1e1e1e;color:#b4b4b4">(</span><span style="background:#1e1e1e;color:#d69d85">"figura::draw"</span><span style="background:#1e1e1e;color:#b4b4b4">);</span>
</li>
<li>
<span style="background:#1e1e1e;color:#dcdcdc"> }</span>
</li>
<li>
<span style="ba
ckground:#1e1e1e;color:#dcdcdc">};
var rect = Object.create(figura);
rect.draw = function() {
console.log(“rect::draw”);
};
function Polimorfismo(figura) {
figura.draw();
}
Polimorfismo(rect);