La programación basada en prototipos es un estilo de programación orientada a objetos en el que la reutilización de comportamientos (conocida como herencia ) se realiza mediante la reutilización de objetos existentes que sirven como prototipos . Este modelo también se conoce como programación prototípica , orientada a prototipos, sin clases o basada en instancias .
La programación basada en prototipos utiliza el proceso de objetos generalizados, que luego pueden clonarse y extenderse. Tomando como ejemplo la fruta, un objeto "fruta" representaría las propiedades y la funcionalidad de la fruta en general. Un objeto "plátano" se clonaría a partir del objeto "fruta" y se le añadirían propiedades generales específicas de los plátanos. Cada objeto "plátano" individual se clonaría a partir del objeto genérico "plátano". Compárese con el paradigma basado en clases , donde una clase "fruta" se extendería mediante una clase "plátano" .
Historia
El primer lenguaje de programación basado en prototipos fue KRL, de Daniel G. Bobrow y Terry Winograd . El documento AIM-293 de Stanford de 1976 lo convierte en el primer lenguaje en adoptar el término orientado a objetos en el sentido moderno, el que introdujo formalmente el término " herencia " ("de propiedades"), implementando de hecho la herencia múltiple , y el que introdujo el término "prototipo" con su significado actual.
Otros sistemas prototipo iniciales incluyen Director, también conocido como Ani (sobre MacLisp ) (1976-1979), y de forma contemporánea y no independiente, ThingLab (sobre Smalltalk ) (1977-1981), proyectos de doctorado respectivos de Kenneth Michael Kahn en el MIT y Alan Hamilton Borning en Stanford (pero trabajando con Alan Kay en Xerox PARC). El equipo de Actors de Hewitt en el MIT añadió soporte para Prototype OO a ACT-1 en 1979 basándose en el trabajo de Kahn, llamando al mecanismo subyacente "delegación" para distinguirlo del soporte Class OO que también añadieron como "herencia". Borning popularizó la palabra "prototipo" en este contexto en su artículo de 1981 en ACM Transactions on Programming Languages and Systems (TOPLAS) . Mientras tanto, Rees y Adams implementaron la programación orientada a objetos sin clases utilizando cierres léxicos en Yale T Scheme (1981-1989); no la denominaron "prototipo", pero era isomorfa. Posteriormente, en 1992, Ken Dickey publicó su modelo de programación orientada a objetos como código Scheme portable bajo el nombre de YASOS.
El lenguaje que popularizó el concepto de prototipos entre los académicos fue Self (1985-1995), desarrollado por David Ungar y Randall Smith. Pero el lenguaje que hizo que los prototipos fueran omnipresentes para todos fue JavaScript, de Brendan Eich (1995-actualidad).
Desde finales de la década de 1990, el paradigma sin clases se ha vuelto cada vez más popular. Algunos lenguajes actuales orientados a prototipos son JavaScript (y otras implementaciones de ECMAScript como JScript y ActionScript 1.0 de Flash ), Lua , Cecil , NewtonScript , Io , Ioke , MOO , REBOL y AHK .
Desde la década de 2010, ha surgido una nueva generación de lenguajes con prototipos puramente funcionales que reducen la POO a su esencia: Jsonnet es un lenguaje dinámico y perezoso con un sistema de objetos prototipo integrado que utiliza herencia mixin ; Nix es un lenguaje dinámico y perezoso que construye un sistema de objetos equivalente (extensiones de Nix) con solo dos definiciones de funciones breves (además de muchas otras funciones de conveniencia). Ambos lenguajes se utilizan para definir grandes configuraciones de software distribuidas (Jsonnet se inspira directamente en GCL, el lenguaje de configuración de Google, con el que Google define todos sus despliegues, y tiene una semántica similar, aunque con enlace dinámico de variables). Desde entonces, otros lenguajes como Gerbil Scheme han implementado sistemas de prototipos perezosos puramente funcionales basados en principios similares.
Diseño e implementación
Etimológicamente, «prototipo» significa «primer molde» (en el sentido de fabricado). Un prototipo es un objeto concreto a partir del cual se pueden crear otros objetos mediante copia y modificación. Por ejemplo, el Prototipo Internacional del Kilogramo es un objeto real que existe, a partir del cual se pueden crear nuevos objetos kilogramo mediante copia. En comparación, una «clase» es un concepto abstracto al que pueden pertenecer los objetos. Por ejemplo, todos los objetos kilogramo pertenecen a la clase KilogramObject, que podría ser una subclase de MetricObject, y así sucesivamente.
La herencia prototípica en JavaScript es descrita por Douglas Crockford como
Se crean objetos prototipo y luego… se crean nuevas instancias. Los objetos son mutables en JavaScript, por lo que podemos ampliar las nuevas instancias, añadiéndoles nuevos campos y métodos. Estos pueden actuar como prototipos para objetos aún más nuevos. No necesitamos clases para crear muchos objetos similares… Los objetos heredan de otros objetos. ¿Qué podría ser más orientado a objetos que eso? [ 1 ]
Los defensores de la programación basada en prototipos argumentan que anima al programador a centrarse en el comportamiento de un conjunto de ejemplos y solo después preocuparse por clasificar estos objetos en objetos arquetípicos que luego se utilizan de forma similar a las clases . [ 2 ] Muchos sistemas basados en prototipos fomentan la alteración de prototipos durante el tiempo de ejecución , mientras que muy pocos sistemas orientados a objetos basados en clases (como el sistema orientado a objetos dinámico, Common Lisp , Dylan , Objective-C , Perl , Python , Ruby o Smalltalk ) permiten que las clases se alteren durante la ejecución de un programa.
Casi todos los sistemas basados en prototipos se basan en lenguajes interpretados y de tipado dinámico . Sin embargo, los sistemas basados en lenguajes de tipado estático también son técnicamente viables. El lenguaje Omega, descrito en Programación basada en prototipos [ 3 ] , es un ejemplo de este tipo de sistema, aunque, según el sitio web de Omega, este no es exclusivamente estático, sino que su compilador puede optar por el enlace estático cuando sea posible y mejore la eficiencia del programa.
Construcción de objetos
En los lenguajes basados en prototipos no existen clases explícitas. Los objetos heredan directamente de otros objetos a través de una propiedad de prototipo. Esta propiedad se denomina prototypeen Self y JavaScript , o protoen Io . Existen dos métodos para construir nuevos objetos: la creación de objetos ex nihilo ("desde la nada") o la clonación de un objeto existente. El primero se admite mediante algún tipo de literal de objeto , declaraciones donde los objetos se pueden definir en tiempo de ejecución a través de una sintaxis especial {...}y se pasan directamente a una variable. Si bien la mayoría de los sistemas admiten diversas formas de clonación, la creación de objetos ex nihilo no es tan común. [ 4 ]
En los lenguajes basados en clases, una nueva instancia se construye mediante la función constructora de la clase , una función especial que reserva un bloque de memoria para los miembros del objeto (propiedades y métodos) y devuelve una referencia a dicho bloque. Se puede pasar un conjunto opcional de argumentos al constructor , que generalmente se almacenan en propiedades. La instancia resultante heredará todos los métodos y propiedades definidos en la clase, la cual actúa como una especie de plantilla a partir de la cual se pueden construir objetos del mismo tipo.
Los sistemas que admiten la creación de objetos ex nihilo permiten crear nuevos objetos desde cero sin clonar un prototipo existente. Estos sistemas proporcionan una sintaxis especial para especificar las propiedades y comportamientos de los nuevos objetos sin hacer referencia a los existentes. En muchos lenguajes de prototipos existe un objeto raíz, a menudo llamado Object , que se establece como prototipo predeterminado para todos los demás objetos creados en tiempo de ejecución y que contiene métodos de uso común, como una toString()función para devolver una descripción del objeto como una cadena. Un aspecto útil de la creación de objetos ex nihilo es asegurar que los nombres de las ranuras (propiedades y métodos) de un nuevo objeto no tengan conflictos de espacio de nombres con el objeto Object de nivel superior . (En JavaScript , esto se puede lograr utilizando un prototipo nulo, es decir Object.create(null), ).
La clonación se refiere a un proceso mediante el cual se construye un nuevo objeto copiando el comportamiento de un objeto existente (su prototipo). El nuevo objeto hereda entonces todas las cualidades del original. A partir de este punto, el nuevo objeto puede modificarse. En algunos sistemas, el objeto hijo resultante mantiene un vínculo explícito (mediante delegación o semejanza ) con su prototipo, y los cambios en el prototipo provocan cambios correspondientes que se hacen evidentes en su clon. Otros sistemas, como el lenguaje de programación Kevo , similar a Forth , no propagan los cambios desde el prototipo de esta manera y, en cambio, siguen un modelo más concatenativo donde los cambios en los objetos clonados no se propagan automáticamente a través de los descendientes. [ 2 ]
// Ejemplo de herencia prototípica verdadera en JavaScript.// Creación de objetos usando la notación literal de objetos {}. const foo = { name : "foo" , one : 1 , two : 2 };// Otro objeto. const bar = { two : "dos" , three : 3 };// Object.setPrototypeOf() es un método introducido en ECMAScript 2015. // Para simplificar, supongamos que la siguiente // línea funciona independientemente del motor utilizado: Object . setPrototypeOf ( bar , foo ); // foo ahora es el prototipo de bar.// Si intentamos acceder a las propiedades de foo desde bar de ahora en adelante, // lo lograremos. bar . one ; // Se resuelve en 1.// Las propiedades del objeto hijo también son accesibles. bar . three ; // Se resuelve en 3.// Propiedades propias ocultan las propiedades del prototipo. bar . two ; // Se resuelve en "two". bar . name ; // No se ve afectado, se resuelve en "foo". foo . name ; // Se resuelve en "foo".Por otro ejemplo:
const foo = { one : 1 , two : 2 };// bar.[[prototype]] = foo const bar = Object . create ( foo );barra . tres = 3 ;barra . uno ; // 1 barra . dos ; // 2 barra . tres ; // 3Delegación
En los lenguajes basados en prototipos que utilizan delegación , el entorno de ejecución del lenguaje puede enviar el método correcto o encontrar el dato adecuado simplemente siguiendo una serie de punteros de delegación (desde el objeto hasta su prototipo) hasta encontrar una coincidencia. Todo lo que se requiere para establecer este comportamiento compartido entre objetos es el puntero de delegación. A diferencia de la relación entre clase e instancia en los lenguajes orientados a objetos basados en clases, la relación entre el prototipo y sus ramificaciones no requiere que el objeto hijo tenga una similitud estructural o de memoria con el prototipo más allá de este vínculo. Por lo tanto, el objeto hijo puede seguir modificándose y enmendándose con el tiempo sin reorganizar la estructura de su prototipo asociado, como ocurre en los sistemas basados en clases. También es importante señalar que no solo se pueden agregar o cambiar datos, sino también métodos. Por esta razón, algunos lenguajes basados en prototipos se refieren tanto a los datos como a los métodos como "ranuras" o "miembros".
Concatenación
En el prototipado concatenativo —el enfoque implementado por el lenguaje de programación Kevo— no existen punteros ni enlaces visibles al prototipo original a partir del cual se clona un objeto. El objeto prototipo (padre) se copia en lugar de enlazarse con él, y no hay delegación. Como resultado, los cambios en el prototipo no se reflejarán en los objetos clonados. [ 5 ] Cabe mencionar que el lenguaje de programación Cosmos logra lo mismo mediante el uso de estructuras de datos persistentes . [ 6 ]
La principal diferencia conceptual de este sistema radica en que los cambios realizados en un objeto prototipo no se propagan automáticamente a los clones. Esto puede considerarse una ventaja o una desventaja. (Sin embargo, Kevo proporciona primitivas adicionales para publicar cambios en conjuntos de objetos en función de su similitud —el llamado mecanismo de semejanza familiar o de familia de clones [ 5 ] — en lugar de basarse en el origen taxonómico, como es habitual en el modelo de delegación). También se suele afirmar que el prototipado basado en delegación presenta una desventaja adicional: los cambios en un objeto hijo pueden afectar al funcionamiento posterior del padre. No obstante, este problema no es inherente al modelo de delegación ni existe en lenguajes de delegación como JavaScript, que garantizan que los cambios en un objeto hijo se registren siempre en el propio objeto hijo y nunca en los padres (es decir, el valor del hijo refleja el del padre en lugar de modificarlo).
En implementaciones simples, el prototipado concatenativo tendrá una búsqueda de miembros más rápida que el prototipado basado en delegación (porque no hay necesidad de seguir la cadena de objetos padre), pero a la inversa usará más memoria (porque se copian todas las ranuras, en lugar de haber una sola ranura que apunte al objeto padre). Sin embargo, las implementaciones más sofisticadas pueden evitar este problema, aunque se requieren compensaciones entre velocidad y memoria. Por ejemplo, los sistemas con prototipado concatenativo pueden usar una implementación de copia en escritura para permitir el intercambio de datos en segundo plano, y este enfoque es de hecho seguido por Kevo. [ 7 ] Por el contrario, los sistemas con prototipado basado en delegación pueden usar el almacenamiento en caché para acelerar la búsqueda de datos.
Crítica
Los defensores de los modelos de objetos basados en clases que critican los sistemas basados en prototipos suelen tener preocupaciones similares a las que los partidarios de los sistemas de tipos estáticos para lenguajes de programación tienen respecto a los sistemas de tipos dinámicos (véase tipo de dato ). Por lo general, estas preocupaciones giran en torno a la corrección , la seguridad , la previsibilidad , la eficiencia y la falta de familiaridad del programador.
En los tres primeros puntos, las clases suelen considerarse análogas a los tipos (en la mayoría de los lenguajes orientados a objetos con tipado estático cumplen esa función) y se proponen para proporcionar garantías contractuales a sus instancias, y a los usuarios de sus instancias, de que se comportarán de una manera determinada.
En cuanto a la eficiencia, la declaración de clases simplifica muchas optimizaciones del compilador que permiten desarrollar búsquedas eficientes de métodos y variables de instancia. Para el lenguaje Self , se invirtió mucho tiempo de desarrollo en el desarrollo, la compilación y la interpretación de técnicas para mejorar el rendimiento de los sistemas basados en prototipos frente a los sistemas basados en clases.
Una crítica común contra los lenguajes basados en prototipos es que la comunidad de desarrolladores de software no está familiarizada con ellos, a pesar de la popularidad y la penetración de mercado de JavaScript . Sin embargo, el conocimiento sobre los sistemas basados en prototipos está aumentando con la proliferación de frameworks de JavaScript y el uso complejo de JavaScript a medida que la World Wide Web (Web) madura. [ 8 ] ECMAScript 6 introdujo clases como azúcar sintáctico sobre la herencia basada en prototipos existente de JavaScript, proporcionando una forma alternativa de crear objetos y gestionar la herencia. [ 9 ]
Lenguajes que admiten la programación basada en prototipos
- Lenguaje concurrente basado en actores (ABCL): ABCL/1 , ABCL/R , ABCL/R2 , ABCL/c+
- Ágora
- AutoHotkey
- Cecil y Diesel de Craig Chambers
- ColdC
- REAJUSTE SALARIAL
- Common Lisp , con Sheeple, CommonORBIT u otras bibliotecas.
- Cian
- ECMAScript
- ActionScript 1.0, utilizado por Adobe Flash y Adobe Flex.
- ECMAScript para XML (E4X)
- JavaScript
- JScript
- Mecanografiado
- Haskell , con el OOHaskell de Kiselyov y Lämmel, o el Aro de Gale.
- Yo
- Ioke
- Jsonnet
- Logtalk
- LPC
- Lua
- M2000
- Arce
- MUGIR
- Neko
- NewtonScript
- Nim
- Nix (donde los prototipos se denominan "extensiones" o "personalizaciones")
- Lisp de objetos
- Oblicuo
- Omega
- OpenLaszlo
- Perl , con el módulo Class::Prototyped
- R , con el paquete proto
- REBOL
- Rojo
- Rubí
- Esquema , con YASOS, Gerbil-POO u otras bibliotecas.
- Ser
- Seph
- Pizarra
- Rana inteligente
- ¡Quebrar!
- Juguetes electrónicos
- TADS
- Tcl con extensión snit
- Umajin [ 10 ]
Véase también
Referencias
- ↑ Crockford, Douglas. "Herencia prototípica en JavaScript" . Consultado el 22 de junio de 2021 .
- 1 2 Taivalsaari, Antero (1996). "Sección 1.1". Clases vs. Prototipos: Algunas observaciones filosóficas e históricas . pp. 44– 50. CiteSeerX 10.1.1.56.4713 .
- ↑ Blaschek, Günther. "Sección 2.8". Omega: Prototipos tipificados estáticamente . pág. 177.
- ↑ Dony, Christophe; Malenfan, Jacques; Bardou, Daniel. "Sección 1.2" (PDF) . Clasificación de lenguajes de programación basados en prototipos . pág. 17.
- 1 2 Antero Taivalsaar (2009). "Simplificando JavaScript con herencia de prototipos basada en concatenación" (PDF) . Universidad Tecnológica de Tampere. Archivado del original (PDF) el 13 de agosto de 2011. Recuperado el 11 de marzo de 2015. Kevo
implementó un modelo de objetos basado en concatenación pura en el que los nuevos objetos se creaban mediante copia y los espacios de nombres de todos los objetos siempre eran completamente autocontenidos. … Además, Kevo tenía un mecanismo interno
de familia de clones
que permitía rastrear la "genealogía" de los cambios entre grupos de objetos, de modo que los cambios en objetos individuales podían propagarse a otros objetos cuando fuera necesario.
- ↑ "Programación orientada a objetos" . GitHub . Consultado el 4 de septiembre de 2023 .
- ↑ Taivalsaari, Antero (1992). "Kevo, un lenguaje de programación orientado a objetos basado en prototipos, concatenación y operaciones de módulos". Informe técnico LACIR 92-02 . Universidad de Victoria.
- ↑ "Programación orientada a objetos prototípica usando JavaScript" . A List Apart . 26 de abril de 2016. Consultado el 21 de octubre de 2018 .
- ↑ "Clases" . Referencia de JavaScript . Mozilla Developer Network . Consultado el 9 de febrero de 2016 .
- ↑ Lenguaje de scripting propietario. En http://www.davidbrebner.com/?p=4 encontrará algunos ejemplos básicos de uso.
Lecturas adicionales
- Abadi, Martín ; Luca Cardelli (1996). Una teoría de los objetos . Springer-Verlag. ISBN 978-1-4612-6445-3.
- Lucha de clases: Clases vs. Prototipos , por Brian Foote.
- Noble, James; Taivalsaari, Antero; Moore, Ivan, eds. (1999). Programación basada en prototipos: conceptos, lenguajes y aplicaciones . Springer-Verlag. ISBN 981-4021-25-3.
- Uso de objetos prototípicos para implementar comportamiento compartido en sistemas orientados a objetos , por Henry Lieberman, 1986.
- Programación basada en prototipos
- Programación orientada a objetos
- paradigmas de programación
- teoría de tipos
