¿Cuáles son las ventajas de la OOP basada en prototipos sobre la OOP basada en clases?


47

Cuando comencé a programar Javascript por primera vez después de tratar principalmente con OOP en el contexto de lenguajes basados ​​en clases, me quedé confundido sobre por qué alguna vez se preferiría la OOP basada en prototipos a la OOP basada en clases.

  1. ¿Cuáles son las ventajas estructurales de usar OOP basado en prototipos, si hay alguno? (por ejemplo, ¿esperaríamos que sea más rápido o que requiera menos memoria en ciertas aplicaciones?)
  2. ¿Cuáles son las ventajas desde la perspectiva de un codificador? (por ejemplo, ¿es más fácil codificar ciertas aplicaciones o extender el código de otras personas mediante la creación de prototipos?)

No considere esta pregunta como una pregunta sobre Javascript en particular (que ha tenido muchas fallas a lo largo de los años que no tienen relación alguna con la creación de prototipos). En cambio, míralo en el contexto de las ventajas teóricas de la creación de prototipos frente a las clases.

Gracias.


Respuestas:


46

Tenía bastante experiencia en ambos enfoques al escribir un juego de rol en Java. Originalmente escribí todo el juego usando OOP basado en clases, pero finalmente me di cuenta de que este era el enfoque incorrecto (se estaba volviendo imposible de mantener a medida que se expandía la jerarquía de clases). Por lo tanto, convertí toda la base de código a código basado en prototipos. El resultado fue mucho mejor y más fácil de gestionar.

Código fuente aquí si está interesado ( Tyrant - Java Roguelike )

Aquí están los principales beneficios:

  • Es trivial crear nuevas "clases" : simplemente copie el prototipo y cambie un par de propiedades y listo ... nueva clase. Utilicé esto para definir un nuevo tipo de poción, por ejemplo, en 3-6 líneas de Java cada una. ¡Mucho mejor que un nuevo archivo de clase y un montón de repeticiones!
  • Es posible construir y mantener un número extremadamente grande de "clases" con un código relativamente pequeño : Tyrant, por ejemplo, tenía algo así como 3000 prototipos diferentes con solo alrededor de 42,000 líneas de código en total. ¡Eso es bastante sorprendente para Java!
  • La herencia múltiple es fácil : simplemente copie un subconjunto de las propiedades de un prototipo y péguelo sobre las propiedades de otro prototipo. Por ejemplo, en un juego de rol, es posible que desee que un "golem de acero" tenga algunas de las propiedades de un "objeto de acero" y algunas de las propiedades de un "golem" y algunas de las propiedades de un "monstruo no inteligente". Fácil con prototipos, intente hacerlo con una jerarquía de herencia ......
  • Puede hacer cosas inteligentes con modificadores de propiedad : al poner una lógica inteligente en el método genérico de "propiedad de lectura", puede implementar varios modificadores. Por ejemplo, fue fácil definir un anillo mágico que agregara +2 de fuerza a quien lo usara. La lógica para esto estaba en el objeto del anillo, no en el método de "fuerza de lectura", por lo que evitó tener que poner muchas pruebas condicionales en otra parte de su base de código (por ejemplo, "¿el personaje lleva un anillo de aumento de fuerza?")
  • Las instancias pueden convertirse en plantillas para otras instancias ; por ejemplo, si desea "clonar" un objeto, es fácil, simplemente use el objeto existente como prototipo para el nuevo objeto. No es necesario escribir mucha lógica de clonación compleja para diferentes clases.
  • Es bastante fácil cambiar el comportamiento en tiempo de ejecución , es decir, puede cambiar las propiedades y "transformar" un objeto de forma bastante arbitraria en tiempo de ejecución. Permite efectos geniales en el juego, y si combinas esto con un "lenguaje de secuencias de comandos", entonces casi todo es posible en tiempo de ejecución.
  • Es más adecuado para un estilo de programación "funcional" : tiende a encontrarse escribiendo muchas funciones que analizan objetos y actúan de manera apropiada, en lugar de la lógica incrustada en métodos asociados a clases específicas. Personalmente prefiero este estilo FP.

Aquí están los principales inconvenientes:

  • Pierde la seguridad de la escritura estática , ya que está creando efectivamente un sistema de objetos dinámico. Esto tiende a significar que necesita escribir más pruebas para garantizar que el comportamiento sea correcto y que los objetos sean del "tipo" correcto
  • Hay una sobrecarga de rendimiento : dado que las lecturas de las propiedades de los objetos generalmente se ven obligadas a pasar por una o más búsquedas de mapas, usted paga un pequeño costo en términos de rendimiento. En mi caso, no fue un problema, pero podría ser un problema en algunos casos (por ejemplo, un FPS 3D con una gran cantidad de objetos que se consultan en cada cuadro)
  • Las refactorizaciones no funcionan de la misma manera : en un sistema basado en un prototipo, esencialmente está "construyendo" su jerarquía de herencia con código. Las herramientas de IDE / refactorización realmente no pueden ayudarlo, ya que no pueden asimilar su enfoque. Nunca encontré esto como un problema, pero podría salirse de control si no tienes cuidado. ¡Probablemente quiera que las pruebas verifiquen que su jerarquía de herencia se está construyendo correctamente!
  • Es un poco extraño : las personas acostumbradas a un estilo OOP convencional pueden confundirse fácilmente. "¿Qué quieres decir con que solo hay una clase llamada" Cosa "?" - "¡¿Cómo extiendo esta clase final de Thing!?!" - "¡Estás violando los principios de la OOP!" - "¡Está mal tener todas estas funciones estáticas que actúan sobre cualquier tipo de objeto!?!?"

Finalmente algunas notas de implementación:

  • Utilicé un Java HashMap para las propiedades y un puntero "padre" para el prototipo. Esto funcionó bien, pero tenía las siguientes desventajas: a) las lecturas de propiedades a veces tenían que rastrearse a través de una larga cadena principal, lo que perjudicaba el rendimiento b) si mutaba un prototipo principal, el cambio afectaría a todos los niños que no habían anulado la propiedad cambiante. ¡Esto puede causar errores sutiles si no tienes cuidado!
  • Si volviera a hacer esto, usaría un mapa persistente inmutable para las propiedades (algo así como los mapas persistentes de Clojure ), o mi propia implementación de mapa hash persistente de Java . Entonces obtendría el beneficio de copias / cambios baratos junto con un comportamiento inmutable y no necesitaría vincular objetos permanentemente con sus padres.
  • Puede divertirse si integra funciones / métodos en las propiedades del objeto. El truco que utilicé en Java para esto (subtipos anónimos de una clase "Script") no fue muy elegante; si lo hiciera nuevamente, probablemente usaría un lenguaje apropiado fácil de insertar para scripts (Clojure o Groovy)


(+1) Es un buen análisis. Altought, está más basado en el modelo Java, por ejemplo, Delphi, C #, VB.Net tiene propiedades explícitas.
umlcat

3
@umlcat: creo que el modelo Java es más o menos el mismo que el modelo Delphi / C # (aparte del agradable azúcar sintáctico para el acceso a la propiedad): aún tiene que declarar estáticamente las propiedades que desea en su definición de clase. El punto de un prototipo de modelo es que esta definición no es estática y que no tiene que hacer ninguna declaración de antemano ....
mikera

Esto se perdió uno grande. Puede alterar el prototipo, que altera efectivamente las propiedades en cada instancia, incluso después de que se crean sin tocar el constructor del prototipo.
Erik Reppen

En cuanto a que la herencia múltiple es más fácil, la comparó con Java, que no admite la herencia múltiple, pero ¿es más fácil en comparación con los lenguajes que lo admiten, como C ++?
Piovezan

2

La principal ventaja de la OOP basada en prototipos es que los objetos y las "clases" se pueden extender en tiempo de ejecución.

En la OOP basada en la clase, hay varias características buenas, desafortunadamente, depende del lenguaje de programación.

Object Pascal (Delphi), VB.Net & C # tiene una forma muy directa de usar propiedades (que no deben confundirse con los campos) y métodos de acceso a las propiedades, mientras que Java y C ++, las propiedades son accedidas por métodos. Y PHP tiene una mezcla de ambos, llamados "métodos mágicos".

Hay algunas clases de escritura dinámicas, aunque los lenguajes OO de la clase principal tienen escritura estática. Creo que la escritura estática con Class OO es muy útil, porque permite una característica llamada Object Introspection que permite crear esos IDE y desarrollar páginas web, visualmente y rápidamente.


0

Tengo que estar de acuerdo con @umlcat. La extensión de clase es una gran ventaja. Por ejemplo, suponga que desea agregar más funcionalidad a una clase de cadena durante un largo período de tiempo. En C ++, haría esto a través de la herencia continua de generaciones anteriores de clases de cadena. El problema con este enfoque es que cada generación se convierte esencialmente en un tipo diferente propio que puede conducir a una reescritura masiva de bases de código existentes. Con la herencia prototípica, simplemente 'adjunta' un nuevo método a la clase base original ..., no hay clases heredadas masivas y relaciones de herencia en todas partes. Me encantaría ver a C ++ con un mecanismo de extensión similar en su nuevo estándar. Pero su comité parece estar dirigido por personas que desean agregar las características pegadizas y populares.


1
Es posible que desee leer Monoliths "Unstrung" , std::stringya tiene demasiados miembros que deberían ser algoritmos independientes o al menos no miembros no amigos. Y de todos modos, se pueden agregar nuevas funciones miembro sin cambiar el diseño en memoria si se puede modificar la clase original.
Deduplicador
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.