Lenguajes especiales para lenguajes orientados a objetos
Simula 67 admite herencia única, polimorfismo y enlace dinámico parcial de cierta importancia;
Smalltalk admite herencia única, polimorfismo y dinámica vinculante;
EIFFEL admite herencia múltiple, polimorfismo y vinculación dinámica;
C++ admite herencia múltiple, polimorfismo y vinculación dinámica parcial.
Java admite herencia única, polimorfismo y enlace dinámico parcial.
Aunque los conceptos implicados en los cinco idiomas tienen básicamente el mismo significado, la terminología utilizada es diferente.
C#, que también admite herencia única, tiene muchas similitudes con Java y C++... Los lenguajes orientados a objetos basados en clases son la corriente principal en el mundo orientado a objetos. Incluye:
Simula, el primer lenguaje orientado a objetos
Smalltalk, el primer lenguaje que admite escritura dinámica.
C++, la mayoría de sus características basadas en clases se heredan de Simula. Etcétera.
La contraparte de los lenguajes basados en clases son los lenguajes orientados a objetos basados en objetos. El concepto de "basado en objetos" aquí es diferente de llamar a Visual Basic basado en objetos. "Basado en objetos" aquí se refiere a un lenguaje que solo se centra en objetos y no tiene el concepto de clases, como Python.
Clase
Veamos primero la definición de clase:
classcell es
var content: integer: = 0; p>
Método get(): el entero es
Devuelve contenido propio;
Fin;
El grupo de métodos (n: entero) es
self .= n;
End;
End;
Una clase se utiliza para describir las características * * * de todos los objetos. pertenecientes a esta clase. El objeto representado por esta clase de celda tiene una propiedad entera llamada contenido, que se inicializa en 0. También describe dos métodos de manipulación de contenido. Prepararse. El contenido de ambos métodos es muy intuitivo. La variable Self representa el objeto mismo.
La semántica dinámica de los objetos puede entenderse como:
Los objetos se representan internamente como punteros a un conjunto de propiedades. Cualquier operación sobre este objeto operará sobre las propiedades y métodos del objeto a través de este puntero. Cuando se asigna o pasa un objeto como parámetro, solo se pasa un puntero para que * * * se pueda compartir el mismo conjunto de propiedades.
(Tenga en cuenta que algunos lenguajes, como C++, hacen una distinción explícita entre punteros a grupos de propiedades y los propios grupos de propiedades, mientras que otros lenguajes ocultan esta distinción).
Objetos Puede utilizar otros nuevos. Para ser precisos, el nuevo C asigna un conjunto de propiedades y devuelve un puntero a este conjunto de propiedades. A este conjunto de propiedades se le asignan valores iniciales e incluye el código de los métodos definidos por la clase c.
Consideremos los tipos. Para los objetos generados por el nuevo C, registre su tipo como InstanceTypeOf(c). Un ejemplo es:
var mi celda:InstanceTypeOf(cell):=new cell;
Aquí, la clase y el tipo se distinguen introduciendo InstancetypeOf(cell). También podría pensar en la célula en sí como un tipo, pero luego encontrará que esto puede generar confusión.
Búsqueda de métodos
Análisis de métodos Dada una llamada a un método O.M. (...), un proceso llamado análisis de métodos implementado en cada lenguaje es responsable de encontrar el código del método correcto. (Ponente: ¿Recuerdas vtables?).
Intuitivamente, el código de un método puede incrustarse en un único objeto y, en muchos lenguajes orientados a objetos, la sintaxis similar de propiedades y métodos da esta impresión.
Sin embargo, pocos idiomas hacen esto para ahorrar espacio. Un enfoque común es que el lenguaje genera muchos MethodSuites, que pueden ser disfrutados por objetos de la misma clase. El proceso de resolución de métodos sigue punteros a grupos de métodos en el objeto para encontrar métodos.
El análisis del método es más complicado cuando se tiene en cuenta la herencia. Un conjunto de métodos puede consistir en un árbol, y el análisis de un método puede requerir encontrar una secuencia de conjuntos de métodos.
Si hay mucha herencia, el conjunto de métodos puede incluso formar un gráfico dirigido o un ciclo.
La resolución del método puede ocurrir en tiempo de compilación o de ejecución.
En algunos lenguajes, al programador no le importa si un método está incrustado en un objeto o existe en un grupo de métodos. Porque en los lenguajes orientados a objetos basados en clases, generalmente no se admiten todas las características del lenguaje que pueden distinguir entre los dos modos.
Por ejemplo, un método no se puede sacar de un objeto y usarlo como una función como una propiedad. No puede actualizar métodos en objetos como propiedades. (Es decir, los métodos de un objeto se actualizan, mientras que los métodos de otros objetos de la misma clase permanecen sin cambios).
Subclases y herencia.
Las subclases y subclases heredadas, al igual que las clases generales, también se utilizan para describir la estructura de los objetos. Sin embargo, gradualmente logra este objetivo heredando la estructura de otras clases.
Las propiedades de la clase principal se copiarán implícitamente a la subclase, y la subclase también puede agregar nuevas propiedades. En algunos idiomas, una subclase puede incluso anular las propiedades de una clase principal (cambiando el tipo de propiedad).
Los métodos de las clases principales se pueden copiar a subclases o anularse por subclases.
Un ejemplo de subclases de código es el siguiente:
Subclases de celda celda como
var backup:integer:= 0;
El el conjunto de cobertura (n: entero) es
self backup:= self
super set(n) . >
Fin p>
El método restaurar() es
self .= self backup
Fin;
La resolución de métodos con subclases cambia dependiendo de si el lenguaje es estático o dinámico.
En lenguajes de tipo estático (como C++, Java), la topología del conjunto de métodos de la clase principal y la subclase se determina en el momento de la compilación, por lo que los métodos en el conjunto de métodos de la clase principal pueden fusionarse en el conjunto de métodos de la subclase, no es necesario buscar en el árbol o gráfico del conjunto de métodos al analizar el método. (Presione: vtable de C++ es este método)
Para los lenguajes escritos dinámicamente (es decir, la relación entre las clases principales y las subclases se determina en tiempo de ejecución), los conjuntos de métodos no se pueden fusionar. Por lo tanto, al analizar métodos, debemos buscar a lo largo de este árbol o gráfico dirigido generado dinámicamente hasta encontrar un método adecuado. Si el lenguaje admite herencia múltiple, la búsqueda se vuelve más complicada.
Clase principal y subclase
Del ejemplo anterior, parece que la subclase solo se usa para tomar prestadas algunas definiciones de la clase principal para evitar la duplicación. Sin embargo, cuando se considera la inclusión, las cosas son un poco diferentes. ¿Qué es la tolerancia? Consulte el siguiente ejemplo:
var my cell:InstanceTypeOf(cell):=new cell;
var my reCell:InstanceTypeOf(reCell):= new reCell;
El proceso f(x: InstanceTypeOf(cell)) es...fin;
Mire el siguiente código:
mi celular:= mi recel; p>
f(my recell);
En estas dos líneas de código, la primera línea asigna una variable de tipo InstanceTypeOf(reCell) a una variable de tipo InstanceTypeOf(cell). Por otro lado, la segunda línea utiliza una variable de tipo InstanceTypeOf(reCell) como parámetro y la pasa a una función de tipo InstanceTypeOf(cell).
Este uso es ilegal en lenguajes como Pascal. En lenguajes orientados a objetos, las siguientes reglas son completamente correctas. Este patrón a menudo se denomina polimorfismo de subtipo o polimorfismo de subtipo.
Si C' es una subclase de C y o' es una instancia de C', entonces o' también es una instancia de C.
Más estrictamente:
Si c ' es una subclase de c y o ':c ' es un tipo de instancia, entonces o ':c) es un tipo de instancia.
Después de un análisis cuidadoso de las reglas anteriores, se puede introducir una relación de subtipo que satisfaga la reflexividad y la transitividad entre los tipos de InstanceTypeOf, que se puede definir como
Entonces las reglas anteriores se pueden dividido en dos reglas:
1 Para cualquier a: A, si A
2 tipo de instancia de(c ')<:InstanceTypeOf(c) si y solo si c. ' Es una subclase de c.
La primera regla se llama tolerancia. Es el único criterio para juzgar subtipos (tenga en cuenta que es un subtipo, no una subclase).
La segunda regla se puede llamar subclases-es-subtipos (subclasificar es subtipos, ¿verdad?)
En general, la herencia está relacionada con la subclasificación, por lo que esta regla también se puede llamar: la herencia es un subtipo.
Todos los lenguajes orientados a objetos admiten la inclusión (se puede decir que sin inclusión no está orientado a objetos).
La mayoría de lenguajes orientados a objetos basados en clases no distinguen entre subclases y subtipos. Sin embargo, algunos lenguajes orientados a objetos recientes adoptan una separación de subtipos y subclases. En otras palabras, A es una subclase de B, pero los objetos de la clase A no pueden usarse como objetos de la clase B... (Nota: es un poco como la herencia privada en C++, pero con contenido más rico)
Está bien, hablaré sobre la diferencia entre subclases y subtipos más adelante.
Ahora, echemos un vistazo a este proceso. f. En el caso de la inclusión, ¿cuál es la semántica dinámica del siguiente código?
El proceso f(x: InstanceTypeOf(cell)) es
x .
End; ( my recell);
Cuando myReCell se pasa a F como un objeto de InstanceTypeOf(cell), ¿qué versión del método set es llamada por x.set(3)? ¿Es una colección definida en una celda o una colección definida en una celda?
En este punto, hay dos opciones,
1. staticdispatch (dependiendo del tipo en el momento de la compilación)
2. Envío dinámico (dependiendo del objeto de tiempo de ejecución Tipo real)
(Presione, los amigos que estén familiarizados con C ++ definitivamente sonreirán, no podría ser más simple).
La programación estática no tiene nada que decir.
La programación dinámica tiene una propiedad interesante. En otras palabras, la contención no puede afectar el estado del objeto. Si el estado de este objeto cambia durante la inclusión, como la división de objetos en C++, entonces el método de resolución dinámica puede fallar.
Afortunadamente, esta propiedad es muy beneficiosa tanto para la semántica como para la eficiencia.
(Presione, la división de objetos en C++ inicializará el vptr del nuevo objeto en un puntero vtable de su propio tipo, por lo que no hay problema de análisis dinámico. Pero, de hecho, la división de objetos no se puede llamar inclusiva en absoluto.
En implementaciones de lenguajes específicos, como C++, aunque la inclusión no cambia el estado interno del objeto, el valor del puntero puede cambiar. Esto también es un problema, pero la solución vtable de C++. solo puede hacer esto. También existe una variante del método vtable que puede evitar cambios de puntero y es más eficiente. Este método se explicará en detalle en otro artículo)
Aunque no contiene información de tipo. cambiará el estado del objeto, pero en algunos lenguajes (como Java) ni siquiera tiene ninguna sobrecarga de tiempo de ejecución. Sin embargo, hace que perdamos cierta información de tipo estático.
Por ejemplo, si hay un tipo InstanceTypeOf(Object), no hay propiedades ni métodos definidos en la clase Object. Hay otra clase MyObject, que hereda de Object. Luego, cuando el objeto MyObject se trata como InstanceTypeOf(Object), lo que se obtiene es un objeto vacío inútil sin nada.
Por supuesto, si consideramos una situación menos extrema, como definir un método F en la clase Objeto y MyObject sobrecarga el método F, entonces las propiedades y métodos en MyObject aún se pueden operar indirectamente mediante programación dinámica. . Este también es un enfoque típico del diseño y la programación orientados a objetos.
Desde una perspectiva purista, el despacho dinámico es lo único que debe usarse para manipular propiedades y métodos que han sido olvidados por la inclusión. ¡Elegante y seguro a la vez, toda la gloria es para la programación dinámica! ! !
Sin embargo, para consternación de los puristas, la mayoría de los lenguajes todavía proporcionan algunas propiedades y métodos que verifican el tipo de objeto en tiempo de ejecución, manipulando así propiedades que han sido olvidadas por la inclusión. Este enfoque a menudo se denomina RTTI (Identificación de tipo de tiempo de ejecución). Por ejemplo, dinámica_cast en C++ o instancia de en Java.
En realidad, RTTI es útil. Sin embargo, debido a algunas razones teóricas y metodológicas, se considera que destruye la pureza de la orientación a objetos.
Primero, rompe la abstracción, creando algunos métodos y propiedades que no deben usarse incorrectamente.
En segundo lugar, efectivamente hace que el programa sea más frágil debido a la incertidumbre del tipo de tiempo de ejecución.
En tercer lugar, y quizás lo más importante, hace que el programa sea menos escalable. Al agregar un nuevo tipo, es posible que desee leer detenidamente el código dedynamic_cast o instanciaof y cambiarlos si es necesario para asegurarse de que agregar este nuevo tipo no cause problemas.
Cuando muchas personas mencionan RTTI, siempre se centran en su sobrecarga de tiempo de ejecución. Sin embargo, esta sobrecarga de tiempo de ejecución es realmente insignificante en relación con las deficiencias metodológicas.
Dentro del marco purista (presionar, respirar, mirar lejos, hacer formas profundas), agregar nuevas subclases no requiere cambiar el código existente.
Esta es una muy buena ventaja, especialmente si no tienes todo el código fuente.
En general, si bien RTTI (también conocido como casos típicos) parece ser una característica inevitable, debe usarse con mucha precaución ya que su metodología tiene algunas deficiencias. Gran parte de lo que está sucediendo hoy en los sistemas de tipos de lenguajes orientados a objetos se debe a varios esfuerzos para evitar RTTI.
Por ejemplo, en algunos sistemas de tipos complejos, la autoescritura se puede utilizar en parámetros y valores de retorno para evitar RTTI, que se presentará más adelante.
Covarianza, anticovarianza e invarianza fundamental (covarianza, anticovarianza e invarianza).
En las siguientes secciones, se presentará una técnica de tipo para evitar RTTI. Antes de eso, primero presentamos los conceptos de "covarianza", "anticovarianza" e "invarianza completa".
Covariante
Primero observamos un par de tipos: A * B.
Este tipo admite la operación getA() para devolver el elemento A de este par.
Dar un
¿Por qué? Esto se puede demostrar mediante la propiedad de inclusión:
Supongamos que hay un objeto de tipo A'*B, donde a': a ', b: b, a'*b
Entonces, porque, a '
De esta manera, se define que el tipo A*B es covariante respecto de A..
De la misma manera, también puede demostrarse que A*B es covariante con respecto a B .
Hablando formalmente, la covarianza se define de la siguiente manera:
Dado L(T), aquí, el tipo L consta del tipo T, por lo tanto,
Si t1
Covarianza inversa
Mire una función: A f(B B (La definición del lenguaje funcional puede ser más concisa, que es f:b-->; Respuesta
Entonces, dado a b '
se puede demostrar que b-> A & lt: B '-& gt; Respuesta.
Basado en el espacio sin ninguna deducción.
Por lo tanto, el tipo de parámetro de la función es anticovariante.
El punto contravariante normal se define de la siguiente manera:
Dado L(T), aquí , L. El tipo está compuesto por el tipo T, por lo que
Si t1
es el mismo, se puede demostrar que el tipo de retorno de la función es covariante. No ha cambiado en absoluto.
Entonces considere la función g:a->;A
Aquí a aparece tanto en la posición del parámetro como en la posición de retorno, lo que demuestra que no es ninguna de las dos. covariante ni anticovariante
Esta situación no es ni covariante ni anticovariante
Vale la pena señalar que para el tipo Par en el primer ejemplo, si se admite setA(A). entonces el par se vuelve inmutable.
Especialización de métodos
En la discusión anterior sobre subclases, adoptamos la regla primordial más simple, es decir, el método anulado debe tener la misma firma.
Sin embargo, desde una perspectiva de seguridad tipográfica, esto es innecesario.
De esta manera, siempre que un
classc sea
Método m(x:A):B es...end;
Método m 1 (x 1:a 1):b 1 es...Fin;
Fin;
La subclase de C 'es.