herencia basada en prototipo versus herencia basada en clase

208

En JavaScript, cada objeto es al mismo tiempo una instancia y una clase. Para hacer la herencia, puede usar cualquier instancia de objeto como prototipo.

En Python, C ++, etc. hay clases e instancias, como conceptos separados. Para hacer la herencia, debe usar la clase base para crear una nueva clase, que luego puede usarse para producir instancias derivadas.

¿Por qué JavaScript se dirigió en esta dirección (orientación a objetos basada en prototipos)? ¿Cuáles son las ventajas (y desventajas) de la OO basada en prototipos con respecto a la OO tradicional basada en clases?

Stefano Borini
fuente
10
JavaScript fue influenciado por Self, que fue el primer idioma con herencia prototípica. En ese momento, la herencia clásica estaba de moda, introducida por primera vez en Simula. Sin embargo, la herencia clásica era demasiado complicada. Luego, David Ungar y Randall Smith tuvieron una epifanía después de leer GEB: "El evento más específico puede servir como un ejemplo general de una clase de eventos". Se dieron cuenta de que las clases no son necesarias para la programación orientada a objetos. Por lo tanto, el Yo nació. Para saber cómo la herencia prototípica es mejor que la herencia clásica, lea esto: stackoverflow.com/a/16872315/783743 =)
Aadit M Shah
@AaditMShah ¿Qué / quién es GEB?
Alex
3
@Alex GEB es un libro escrito por Douglas Hofstadter. Es una abreviatura de Gödel Escher Bach. Kurt Gödel era matemático. Escher era artista. Bach era pianista.
Aadit M Shah

Respuestas:

201

Aquí hay alrededor de un centenar de problemas de terminología, en su mayoría basados ​​en alguien (no usted) que intenta hacer que su idea suene como La mejor.

Todos los lenguajes orientados a objetos deben ser capaces de manejar varios conceptos:

  1. encapsulación de datos junto con operaciones asociadas en los datos, conocidas como miembros de datos y funciones de miembros, o como datos y métodos, entre otras cosas.
  2. herencia, la capacidad de decir que estos objetos son como ese otro conjunto de objetos EXCEPTO por estos cambios
  3. polimorfismo ("muchas formas") en el que un objeto decide por sí mismo qué métodos se ejecutarán, para que pueda depender del idioma para enrutar sus solicitudes correctamente.

Ahora, en cuanto a la comparación:

Lo primero es toda la pregunta "clase" vs "prototipo". La idea comenzó originalmente en Simula, donde con un método basado en clases cada clase representaba un conjunto de objetos que compartían el mismo espacio de estado (leídos "valores posibles") y las mismas operaciones, formando así una clase de equivalencia. Si mira hacia atrás a Smalltalk, dado que puede abrir una clase y agregar métodos, esto es efectivamente lo mismo que puede hacer en Javascript.

Los lenguajes OO posteriores querían poder usar la comprobación de tipo estático, por lo que obtuvimos la noción de un conjunto de clases fijo en tiempo de compilación. En la versión de clase abierta, tenías más flexibilidad; en la versión más reciente, tenía la capacidad de verificar algunos tipos de corrección en el compilador que de lo contrario habrían requerido pruebas.

En un lenguaje "basado en clases", esa copia ocurre en tiempo de compilación. En un lenguaje prototipo, las operaciones se almacenan en la estructura de datos prototipo, que se copia y modifica en tiempo de ejecución. Sin embargo, en resumen, una clase sigue siendo la clase de equivalencia de todos los objetos que comparten el mismo espacio de estado y métodos. Cuando agrega un método al prototipo, efectivamente está haciendo un elemento de una nueva clase de equivalencia.

Ahora, ¿por qué haces eso? principalmente porque crea un mecanismo simple, lógico y elegante en tiempo de ejecución. ahora, para crear un nuevo objeto, o para crear una nueva clase, simplemente tiene que realizar una copia profunda, copiando todos los datos y la estructura de datos prototipo. Entonces obtienes herencia y polimorfismo más o menos de forma gratuita: la búsqueda de métodos siempre consiste en pedirle al diccionario la implementación de un método por nombre.

La razón que terminó en el script Javascript / ECMA es básicamente que cuando comenzamos con esto hace 10 años, estábamos lidiando con computadoras mucho menos potentes y navegadores mucho menos sofisticados. Elegir el método basado en el prototipo significaba que el intérprete podría ser muy simple y al mismo tiempo preservar las propiedades deseables de la orientación a objetos.

Charlie Martin
fuente
1
Bien, ¿se lee ese párrafo como si quisiera decir lo contrario? A Dahl y Nyqvist se les ocurrió "clase" como la colección de cosas con la misma firma de método.
Charlie Martin
1
¿Ese cambio lo dice mejor?
Charlie Martin
2
No, lo siento, CLOS es de finales de los 80 dreamsongs.com/CLOS.html Smalltalk de 1980 en.wikipedia.org/wiki/Smalltalk y Simula con orientación completa de objetos de 1967-68 en.wikipedia.org/wiki/Simula
Charlie Martin
3
@Stephano, no son tan distintos como todo eso: Python, Ruby, Smalltalk usan diccionarios para la búsqueda de métodos, y javascript y Self tienen clases. Hasta cierto punto, podría argumentar que la diferencia es solo que los lenguajes orientados a prototipos están exponiendo sus implementaciones. Por lo tanto, probablemente sea bueno no convertirlo en un gran problema: probablemente sea más como el argumento entre EMACS y vi.
Charlie Martin
21
Respuesta útil . +1 Basura menos útil en los comentarios. Quiero decir, ¿hace alguna diferencia si CLOS o Smalltalk fueron los primeros? La mayoría de las personas aquí no son historiadores de todos modos.
Adam Arold
40

Una comparación, que está ligeramente sesgada hacia el enfoque basado en prototipos, se puede encontrar en el artículo Self: The Power of Simplicity . El documento presenta los siguientes argumentos a favor de los prototipos:

Creación por copia . La creación de nuevos objetos a partir de prototipos se logra mediante una simple operación, copiando, con una simple metáfora biológica, la clonación. La creación de nuevos objetos a partir de clases se realiza mediante instanciación, que incluye la interpretación de la información de formato en una clase. La instanciación es similar a construir una casa desde un plan. Copiar nos atrae como una metáfora más simple que la instanciación.

Ejemplos de módulos preexistentes . Los prototipos son más concretos que las clases porque son ejemplos de objetos en lugar de descripciones de formato e inicialización. Estos ejemplos pueden ayudar a los usuarios a reutilizar módulos haciéndolos más fáciles de entender. Un sistema basado en prototipos permite al usuario examinar a un representante típico en lugar de exigirle que tenga sentido en su descripción.

Soporte para objetos únicos . Self proporciona un marco que puede incluir fácilmente objetos únicos con su propio comportamiento. Dado que cada objeto tiene ranuras con nombre, y las ranuras pueden contener estado o comportamiento, cualquier objeto puede tener ranuras o comportamiento únicos. Los sistemas basados ​​en clases están diseñados para situaciones en las que hay muchos objetos con el mismo comportamiento. No hay soporte lingüístico para que un objeto posea su propio comportamiento único, y es incómodo crear una clase que garantice que solo tenga una instancia [ piense en el patrón singleton ]. Self no sufre ninguna de estas desventajas. Cualquier objeto se puede personalizar con su propio comportamiento. Un objeto único puede contener el comportamiento único, y no se necesita una "instancia" separada.

Eliminación de meta-regresión . Ningún objeto en un sistema basado en clases puede ser autosuficiente; Se necesita otro objeto (su clase) para expresar su estructura y comportamiento. Esto conduce a una meta-regresión conceptualmente infinita: a pointes una instancia de clase Point, que es una instancia de metaclase Point, que es una instancia de metametaclase Point, ad infinitum. Por otro lado, en los sistemas basados ​​en prototipos, un objeto puede incluir su propio comportamiento; No se necesita ningún otro objeto para darle vida. Los prototipos eliminan la meta-regresión.

Self es probablemente el primer idioma en implementar prototipos (también fue pionero en otras tecnologías interesantes como JIT, que más tarde llegó a la JVM), por lo que leer los otros documentos de Self también debería ser instructivo.

Vijay Mathew
fuente
55
RE: Eliminación de meta-regresión: en el Common Lisp Object System, que está basado en clases, a pointes una instancia de clase Point, que es una instancia de metaclase standard-class, que es una instancia de sí misma, ad finitum.
Max Nanasy
Los enlaces a un autoevaluación están muertos. Enlaces de trabajo: Self: El poder de la simplicidad | A Self Bibliography
user1201917
24

Deberías ver un gran libro en JavaScript de Douglas Crockford . Proporciona una muy buena explicación de algunas de las decisiones de diseño tomadas por los creadores de JavaScript.

Uno de los aspectos de diseño importantes de JavaScript es su sistema de herencia de prototipos. Los objetos son ciudadanos de primera clase en JavaScript, tanto que las funciones regulares también se implementan como objetos (el objeto 'Función' para ser precisos). En mi opinión, cuando se diseñó originalmente para ejecutarse dentro de un navegador, estaba destinado a usarse para crear muchos objetos únicos. En el navegador DOM, encontrará esa ventana, documento, etc., todos los objetos únicos. Además, JavaScript es un lenguaje dinámico tipado libremente (a diferencia de Python, que es fuertemente tipado, lenguaje dinámico), como resultado, se implementó un concepto de extensión de objeto mediante el uso de la propiedad 'prototipo'.

Así que creo que hay algunas ventajas para la OO basada en prototipos implementada en JavaScript:

  1. Adecuado en entornos poco tipados, no es necesario definir tipos explícitos.
  2. Hace que sea increíblemente fácil implementar un patrón único (compare JavaScript y Java a este respecto, y sabrá de lo que estoy hablando).
  3. Proporciona formas de aplicar un método de un objeto en el contexto de un objeto diferente, agregando y reemplazando métodos dinámicamente de un objeto, etc. (cosas que no son posibles en lenguajes fuertemente tipados).

Estas son algunas de las desventajas del prototipo OO:

  1. No hay una manera fácil de implementar variables privadas. Es posible implementar variables privadas usando la magia de Crockford usando cierres , pero definitivamente no es tan trivial como usar variables privadas en Java o C #.
  2. Todavía no sé cómo implementar múltiples herencias (por lo que vale) en JavaScript.
Amit
fuente
2
Simplemente use una convención de nomenclatura para vars privados, como lo hace Python.
aehlke
1
en js, la forma de hacer vars privados es con cierres, y eso es independiente del tipo de herencia que elija.
Benja
66
Crockford ha hecho mucho para dañar JavaScript, ya que un lenguaje de script bastante simple se ha transformado en una fascinación masterbatory con sus componentes internos. JS no tiene un verdadero alcance de palabras clave privadas o una verdadera herencia múltiple: no intente falsificarlas.
Hal50000