stringtranslate.com

Programación basada en prototipos

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 a través de un proceso de reutilización de objetos existentes que sirven como prototipos . Este modelo también se puede conocer como programación prototípica , orientada a prototipos, sin clases o basada en instancias .

La programación basada en prototipos utiliza el proceso de generalización de objetos, que luego se pueden clonar y extender. Si tomamos como ejemplo una fruta, un objeto "fruta" representaría las propiedades y la funcionalidad de la fruta en general. Un objeto "banana" se clonaría a partir del objeto "fruta" y se agregarían propiedades generales específicas de las bananas. Cada objeto "banana" individual se clonaría a partir del objeto "banana" genérico. Compárese con el paradigma basado en clases , donde una clase "fruta" se extendería mediante una clase "banana" .

Los primeros lenguajes de programación basados ​​en prototipos fueron Director, también conocido como Ani (sobre MacLisp ) (1976-1979), y de manera contemporánea y no independiente, ThingLab (sobre Smalltalk ) (1977-1981), respectivos proyectos de doctorado de Kenneth Michael Kahn en el MIT y Alan Hamilton Borning en Stanford (pero trabajando con Alan Kay en Xerox PARC). Borning introdujo la palabra "prototipo" en su artículo TOPLAS de 1981. El primer lenguaje de programación basado en prototipos con más de un implementador o usuario fue probablemente Yale T Scheme (1981-1984), aunque al igual que Director y ThingLab inicialmente, solo habla de objetos sin clases. El lenguaje que popularizó el nombre y la noción de prototipos fue Self (1985-1995), desarrollado por David Ungar y Randall Smith para investigar temas en el diseño de lenguajes orientados a objetos.

Desde finales de los años 90, 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 aparecido una nueva generación de lenguajes con prototipos funcionales puros, que reducen la programación orientada a objetos a su núcleo: Jsonnet es un lenguaje funcional puro perezoso dinámico con un sistema de objetos prototipo incorporado que utiliza herencia mixin ; Nix es un lenguaje funcional puro perezoso dinámico que construye un sistema de objetos equivalente ("extensiones" de Nix) en solo dos definiciones de función cortas (más muchas otras funciones de conveniencia). Ambos lenguajes se utilizan para definir grandes configuraciones de software distribuido (Jsonnet se inspira directamente en GCL, el lenguaje de configuración de Google, con el que Google define todas sus implementaciones, 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 funcionales puros perezosos basados ​​en principios similares.

Diseño e implementación

Etimológicamente, un "prototipo" significa "primer molde" ("moldeado" en el sentido de ser fabricado). Un prototipo es una cosa concreta, a partir de la cual se pueden crear otros objetos copiándolos y modificándolos. Por ejemplo, el Prototipo Internacional del Kilogramo es un objeto real que realmente existe, a partir del cual se pueden crear nuevos objetos de kilogramo copiándolos. En comparación, una "clase" es una cosa abstracta, a la que pueden pertenecer los objetos. Por ejemplo, todos los objetos de kilogramo están en la clase de KilogramObject, que podría ser una subclase de MetricObject, y así sucesivamente.

La herencia prototípica en JavaScript es descrita por Douglas Crockford como

Creas objetos prototipo y luego... creas nuevas instancias. Los objetos son mutables en JavaScript, por lo que podemos aumentar las nuevas instancias, dá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 los objetos. ¿Qué podría ser más orientado a objetos que eso? [1]

Los defensores de la programación basada en prototipos argumentan que alienta 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 usan de manera similar a las clases . [2] Muchos sistemas basados ​​en prototipos fomentan la alteración de los prototipos durante el tiempo de ejecución , mientras que solo unos 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 alterar las clases durante la ejecución de un programa.

Casi todos los sistemas basados ​​en prototipos se basan en lenguajes interpretados y tipados dinámicamente . Sin embargo, los sistemas basados ​​en lenguajes tipados estáticamente son técnicamente viables. El lenguaje Omega que se analiza en Prototype-Based Programming [3] es un ejemplo de un sistema de este tipo, aunque según el sitio web de Omega, incluso Omega no es exclusivamente estático, sino que su "compilador puede optar por utilizar enlaces estáticos cuando esto sea posible y puede mejorar la eficiencia de un programa".

Construcción de objetos

En los lenguajes basados ​​en prototipos no hay clases explícitas. Los objetos heredan directamente de otros objetos a través de una propiedad prototipo. La propiedad prototipo se llama prototypeen Self y JavaScript , o protoen Io . Hay dos métodos para construir nuevos objetos: creación de objetos ex nihilo ("de la nada") o mediante la clonación de un objeto existente. El primero se apoya mediante alguna forma de literal de objeto , declaraciones donde los objetos se pueden definir en tiempo de ejecución a través de una sintaxis especial como {...}y pasar directamente a una variable. Si bien la mayoría de los sistemas admiten una variedad de clonaciones, la creación de objetos ex nihilo no es tan prominente. [4]

En los lenguajes basados ​​en clases, se construye una nueva instancia a través de la función constructora de una 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 ese bloque. Se puede pasar a la función un conjunto opcional de argumentos del constructor , que normalmente se guardan en propiedades. La instancia resultante heredará todos los métodos y propiedades que se definieron en la clase, que actúa como una especie de plantilla a partir de la cual se pueden construir objetos con tipos similares.

Los sistemas que admiten la creación de objetos ex nihilo permiten crear nuevos objetos desde cero sin clonar a partir de un prototipo existente. Dichos sistemas proporcionan una sintaxis especial para especificar las propiedades y comportamientos de los nuevos objetos sin hacer referencia a objetos 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 necesarios comúnmente, 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 garantizar que los nombres de los espacios (propiedades y métodos) de un nuevo objeto no tengan conflictos de espacio de nombres con el objeto Object de nivel superior . (En el lenguaje JavaScript , se puede hacer esto 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 entonces lleva todas las cualidades del original. A partir de este punto, el nuevo objeto puede ser modificado. En algunos sistemas, el objeto hijo resultante mantiene un vínculo explícito (a través de delegación o semejanza ) con su prototipo, y los cambios en el prototipo hacen que los cambios correspondientes sean evidentes en su clon. Otros sistemas, como el lenguaje de programación similar a Forth Kevo, no propagan el cambio desde el prototipo de esta manera y en su lugar 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 verdadero estilo de herencia prototípica en JavaScript.// Creación de objetos utilizando la notación de objeto literal {}. const foo = { name : "foo" , one : 1 , two : 2 };          // Otro objeto. const bar = { dos : "dos" , tres : 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, // tendremos éxito. bar . one ; // Se resuelve en 1. // Las propiedades del objeto secundario también son accesibles. bar . three ; // Se resuelve en 3. // Propiedades propias shadow prototipo properties. bar . two ; // Se resuelve en "two". bar . name ; // No se ve afectado, se resuelve en "foo". foo . name ; // Se resuelve en "foo".   

Para otro ejemplo:

const foo = { uno : 1 , dos : 2 };        // bar.[[prototipo]] = foo const bar = Object.create ( foo ) ;   barra . tres = 3 ;  compás . uno ; // 1 compás . dos ; // 2 compás . tres ; // 3   

Delegación

En los lenguajes basados ​​en prototipos que utilizan delegación , el entorno de ejecución del lenguaje es capaz de enviar el método correcto o encontrar la pieza de datos correcta simplemente siguiendo una serie de punteros de delegación (desde el objeto hasta su prototipo) hasta que se encuentra 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 la clase y la 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 memoria o similitud estructural con el prototipo más allá de este vínculo. Como tal, el objeto hijo puede continuar modificándose y enmendándose con el tiempo sin reorganizar la estructura de su prototipo asociado como 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". [ cita requerida ]

Concatenación

En la creación de prototipos concatenativo (el enfoque implementado por el lenguaje de programación Kevo) no hay punteros visibles ni enlaces al prototipo original a partir del cual se clona un objeto. El objeto prototipo (padre) se copia en lugar de vincularse a él y no hay delegación. Como resultado, los cambios en el prototipo no se reflejarán en los objetos clonados. [5] Por cierto, el lenguaje de programación Cosmos logra lo mismo mediante el uso de estructuras de datos persistentes . [6]

La principal diferencia conceptual bajo este arreglo es que los cambios hechos a un objeto prototipo no son propagados automáticamente a los clones. Esto puede ser visto como una ventaja o desventaja. (Sin embargo, Kevo proporciona primitivas adicionales para publicar cambios a través de conjuntos de objetos basados ​​en su similitud —los llamados parecidos familiares o mecanismo de familia de clones [5] — en lugar de a través del origen taxonómico, como es típico en el modelo de delegación). También se afirma a veces que la creación de prototipos basada en delegación tiene una desventaja adicional en el sentido de que los cambios a un objeto hijo pueden afectar el funcionamiento posterior del padre. Sin embargo, este problema no es inherente al modelo basado en delegación y no existe en lenguajes basados ​​en delegación como JavaScript, que aseguran que los cambios a un objeto hijo siempre se registren en el objeto hijo mismo y nunca en los padres (es decir, el valor del hijo eclipsa el valor del padre en lugar de cambiar el valor del padre).

En implementaciones simplistas, 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, utilizará más memoria (porque se copian todas las ranuras, en lugar de que haya una única 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 utilizar una implementación de copia en escritura para permitir el intercambio de datos detrás de escena, y Kevo sigue de hecho este enfoque. [7] Por el contrario, los sistemas con prototipado basado en delegación pueden utilizar 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 tienen los defensores de los sistemas de tipos estáticos para lenguajes de programación con respecto a los sistemas de tipos dinámicos (véase datatype ). Por lo general, dichas preocupaciones tienen que ver con la corrección , la seguridad , la previsibilidad , la eficiencia y la falta de familiaridad del programador.

En los primeros tres 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 propone que proporcionen 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 métodos eficientes y búsquedas de variables de instancia. En el caso del lenguaje Self , se dedicó mucho tiempo de desarrollo a desarrollar, compilar e interpretar técnicas para mejorar el rendimiento de los sistemas basados ​​en prototipos en comparación con los sistemas basados ​​en clases.

Una crítica común que se hace a 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 en el mercado de JavaScript . Sin embargo, el conocimiento sobre los sistemas basados ​​en prototipos está aumentando con la proliferación de marcos de JavaScript y el uso complejo de JavaScript a medida que madura la World Wide Web (Web). [8] [ cita requerida ] 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 administrar la herencia. [9]

Lenguajes que admiten programación basada en prototipos

Véase también

Referencias

  1. ^ Crockford, Douglas. "Herencia prototípica en JavaScript" . Consultado el 22 de junio de 2021 .
  2. ^ ab Taivalsaari, Antero (1996). "Sección 1.1". Clases vs. Prototipos: Algunas observaciones filosóficas e históricas . págs. 44–50. CiteSeerX 10.1.1.56.4713 . 
  3. ^ Blaschek, Günther. "Sección 2.8". Omega: Prototipos estáticamente tipados . pág. 177.
  4. ^ Dony, Christophe; Malenfan, Jacques; Bardou, Daniel. "Sección 1.2" (PDF) . Clasificación de lenguajes de programación basados ​​en prototipos . pág. 17.
  5. ^ de Antero Taivalsaar (2009). "Simplificando JavaScript con herencia de prototipos basada en concatenación" (PDF) . Universidad Tecnológica de Tampere. Archivado desde el original (PDF) el 2011-08-13 . Consultado el 2015-03-11 . 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 autónomos. … Además, Kevo tenía un mecanismo interno de familia de clones que hacía posible rastrear la "genealogía" de los cambios entre grupos de objetos, de modo que los cambios en objetos individuales pudieran propagarse a otros objetos cuando fuera necesario.
  6. ^ "Programación orientada a objetos". GitHub . Consultado el 4 de septiembre de 2023 .
  7. ^ Taivalsaari, Antero (1992). "Kevo, un lenguaje de programación orientado a objetos basado en prototipos y basado en concatenación y operaciones modulares". Informe técnico Informe LACIR 92-02 . Universidad de Victoria.
  8. ^ "Programación orientada a objetos prototípica con JavaScript". A List Apart . 2016-04-26 . Consultado el 2018-10-21 .
  9. ^ "Clases". Referencia de JavaScript . Mozilla Developer Network . Consultado el 9 de febrero de 2016 .
  10. ^ Lenguaje de script propietario. http://www.davidbrebner.com/?p=4 tiene algunos ejemplos básicos de uso.

Lectura adicional

Véase también