La programación orientada a objetos es una nueva forma de ver viejos conceptos de modularidad: hay poco nuevo; pero al adaptar un lenguaje a los planteamientos de POO e incitar a los programadores a utilizarlos, se consigue una mejora de productividad apreciable.
3. Estructuras dinámicas y objetos
Encapsulamiento
· Consiste en agrupar valores con métodos (PROCEDURES y FUNCTIONS).
· La agrupación de valores sigue el esquema de los RECORDS.
· La agrupación de métodos sigue el esquema de la UNITS.
· Dicha combinación se denomina OBJECT.
· Los OBJECTS entran dentro de la categoría de los TYPES; más tarde pueden crearse variables de los tipos declarados.
Ocultación
Los componentes de un OBJECT pueden ser PUBLIC o PRIVATE. Cada una de estas palabras, reservadas, inicia una zona en la que se declaran datos o métodos.
Pueden haber varias zonas públicas y/o varias zonas privadas. No obstante esta libertad, se recomienda disponer únicamente de 2 zonas, una pública al principio y una privada a continuación. La zona pública al principio puede prescindir de la marca PUBLIC. Los objetos quedan pues de la siguiente forma:
TYPE x= object
elementos públicos, datos y/o métodos
private
elementos privados, datos y/o métodos
end;
Casuística:
OBJECT declarado en un PROGRAM:
Todos los campos son utilizables desde cualquier sitio del programa. La distinción entre PUBLIC y PRIVATE es puramente formal. Se aplican las reglas tradicionales de ámbitos de Pascal.
OBJECT declarado en UNIT INTERFACE:
Los campos PUBLIC del OBJECT son utilizables desde cualquier sitio de la UNIT, y en cualquier otra UNIT/PROGRAM que USES esta.
Los campos PRIVATE del OBJECT son utilizables desde cualquier sitio de esta UNIT, pero en ningún otro sitio.
OBJECT declarado en UNIT IMPLEMENTATION:
Todos los campos, sean PUBLIC o PRIVATE, son utilizables desde cualquier sitio de la IMPLEMENTATION. La distinción entre PUBLIC y PRIVATE es puramente formal. Se aplican las reglas tradicionales de ámbitos de Pascal.
Los componentes de un OBJECT, sean PUBLIC o PRIVATE, se pueden usar libremente para programar el contenido de los métodos del OBJECT en cuestión.
Herencia
Un objeto puede ser descendiente de otro. La técnica consiste en coger un objeto ya cerrado, sacar una copia y escribirle cosas encima: más datos y/o más métodos.
Aparece una relación asimétrica de ascendientes/descendientes. La relación es transitiva.
· Los datos de los ascendientes existen en los descendientes; que no pueden definir nuevos campos con igual nombre.
· Los métodos de los ascendientes en principio los pueden usar los descendientes; pero estos pueden redefinirlos de nuevo, ocultando los de sus ascendientes.
·
Se puede llegar a m'etodos de ancestros remotos
explicitando el tipo al que nos referimos. Por ejemplo, si On es un objeto que
procede de O1 a trav'es de varias herencias, los m'etodos de O1 son accesibles
en On como
O1.metodo (...)
·
El ascendiente inmediato es accesible como
INHERITED metodo (...)
Pascal sólo permite heredar de 1 ascendiente. Hay lenguajes que permiten heredar de varios (herencia múltiple)
Asignación de valores
Cuando tenemos variables que son de tipos objeto, sus contenidos pueden asignarse. Con ciertas condiciones.
El contenido de una variable V2 es asignable a una variable V1 (V1:= V2;) si y sólo si
· V1 y V2 son del mismo tipo
· V1 es de un tipo ascendiente del tipo de V2
· Al asignar un objeto a otro objeto, se copian los contenidos de los datos comunes.
· La filosofía subyacente es que un descendiente tiene siempre los componentes de sus ascendientes (inmediatos o remotos) y por tanto la asignación siempre sabe cómo llenar los componentes de destino, aunque haya que tirar algunos que sobran. Al reveés, sin embargo, no sería posible, pues un ascendiente tiene menos cosas que sus descendientes y dejaría campos sin cargar (las variables con contenidos indefinidos son muy peligrosas, fuente de mil errores, y conviene evitarlas a toda costa).
· Los métodos asociados no se alteran: cada objeto mantiene los propios de su tipo.
La asignación de valores tiene lugar
· en sentencias de asignación (ej. V1:= V2;)
·
en el paso de parámetros por valor
(ej. PROCEDURE P (V1: ...); llamado como P (V2); )
Compartición de valores
Se da cuando hay dos formas de acceder a un mismo objeto, cosa que tiene lugar
· al asignar punteros (aparecen aliases apuntando a la misma cosa)
· en el paso de parámetros por referencia (VAR) (aparecen varios nombres para la misma cosa)
Polimorfismo
Se llama polimorfismo a la utilización del mismo nombre para referirse a diferentes cosas.
Hay muchos casos de polimorfismo en Pascal sin objetos, ej:
· Write es un procedimiento polimórfico, pues puede trabajar sobre parámetros de diferentes tipos
· Dos campos que se denominen igual pueden existir en RECORDS diferentes
· Dos variables o procedimientos que se denominen igual pueden existir en ámbitos diferentes; por ejemplo, en diferentes UNITs.
En OBJECTS:
· Dos campos que se denominan igual pueden existir en OBJECTS diferentes, nos referimos tanto a datos como a métodos.
· Un tipo heredado puede redefinir la funcionalidad de un método que se llame igual en algún ascendiente. De hecho, el elemento del ancestro queda oculto por el nuevo elemento.
El polimorfismo es muy natural, intuitivo y carece de emoci'on cuando afecta a objetos no relacionados entre s'i.
El polimorfismo pasa a ser especialmente emocionante cuando se aplica entre objetos que mantienen una relaci'on de herencia. Esto es así porque con el tipo de un ascendiente podemos estar refiriéndonos a una cosa de un tipo descendiente. Como esto sólo se sabe cuando estamos ejecutando el programa, se suele asociar al concepto de "late binding".
Late Binding
[traducible por ligadura tardía, dinámica, ...]
La regla básica es que dado el tipo de un objeto, los métodos que le son
aplicables son los que le son propios, independientemente de que otros
descendientes puedan haberlos redefinido.
En un contexto en el que exista posibilidad de polimorfismo en el sentido del último párrafo del punto anterior, es posible que el método asociado a un objeto no sea necesariamente el asociado a su tipo, sino que podamos referirnos al método asociado al tipo del que es el contenido real de la cosa.
Pascal asocia una serie de palabras novedosas para plasmar cada uno de estos conceptos. La asociación no es todo lo elegante que sería de desear, y a veces hay palabras reservadas que se utilizan para diferentes conceptos, y conceptos que requieren de muchas palabras reservadas.
Encapsulamiento
· OBJECT, que sirve para definir TYPES objeto
· VAR, que sirve para crear variables de TYPES objeto
· SELF, que es una variable que sólo existe dentro de un método y se refiere a la variable sobre la que se aplica el método.
o
En procedures/functions aisladas es necesario
pasar un argumento para saber sobre qué variable se aplica
PROCEDURE pop (VAR p: pila);
al llamarlo como
pop (mipila);
dentro de "pop", la variable externa "mipila" se conoce por p.
o
Como los métodos de los objetos están
estrechamente ligados a los objetos en sí, la asociación es directamente
mipila.pop
y dentro de "pop", la variable externa "mipila" se conoce por SELF.
Ocultación
· PUBLIC, que sirve para dar amplia visibilidad a un componente, dato o método
· PRIVATE, que sirve para acotar la visibilidad de un componente, dato o método
Herencia
· TYPE descendiente= OBJECT (ascendiente) declara un tipo derivado de otro
· INHERITED, que permite que un descendiente se refiera a un método de su ascendiente inmediato
Asignación de valores
· := en asignación convencional
· paso de parámetros por valor
Compartición de valores
· := de punteros (aliases)
· paso de parámetros por referencia (VAR)
Polimorfismo
Late Binding
VIRTUAL
1. palabra que se utiliza adjetivar un método de forma que sea susceptible de un "late binding".
2. Los métodos no VIRTUAL se asocian en base al continente.
3. Los métodos VIRTUAL se asocian en base al contenido.
CONSTRUCTOR
1. Si un objeto tiene métodos VIRTUAL, también debe tener métodos CONSTRUCTOR.
2. Cualquier método (PROCEDURE o FUNCTION) puede ser CONSTRUCTOR, y debe haber alguno para ser llamado antes de usar algún método virtual.
3. Si sobre una variable de un cierto tipo OBJECT se intenta utilizar un método VIRTUAL antes de utilizar algún método CONSTRUCTOR, ocurre un error.
4. Se puede activar un chequeo {$R+} para que el programa de un error si se intenta utilizar un VIRTUAL antes que un CONSTRUCTOR.
5. Probablemente sea suicida trabajar sin {$R+}
6. Todo esto son manías de Turbo Pascal; no ocurre necesariamente en otros lenguajes.
DESTRUCTOR
1. Cualquier método puede ser DESTRUCTOR.
2. Estos métodos sirven para indicarle a la máquina que cuando libere memoria (con DISPOSE) lo haga bien.
3. Sólo son necesarios cuando se trabaja con objetos ubicados dinámicamente y que son polimórficos.
4. En estos casos es obligado que el DESTRUCTOR sea además VIRTUAL.
La utilización de técnicas orientadas a objetos no altera sustancialmente las técnicas clásicas en cuanto a programación con estructuras dinámicas y punteros como mecanismo de referencia a las mismas en tiempo de ejecución. Las mismas estructuras que veníamos utilizando se pueden envolver como objetos y tratar como tipos abstractos de datos. Es decir, tenemos una forma alternativa de encapsulamiento o mdularización.
Pero si aparecen nuevas posibilidades en torno a los conceptos de polimorfismo y ligadura tardía ("late binding").
Los objetos introducen una posibilidad nueva: los métodos virtuales, que aprovechan la ignorancia del programa respecto de lo que un puntero está apuntando para introducir un concepto de genericidad en la estructuración de programas. Más claramente: la esencia de los objetos es asociar los métodos (procedimientos y funciones) a los datos. Esta asociación se puede realizar a través de un puntero: dado un puntero, se persigue para localizar el dato y de él averiguamos qué método aplicar. De esta técnica podemos "abusar" de la tolerancia del sistema a base de pasar punteros de un sitio para otro y sólo en el último momento ("late binding") preocuparnos de averiguar a qué estamos apuntando. Si somos capaces de relajar el chequeo de tipos, podemos encontrarnos en tiempo de ejecución con una variedad de objetos diferentes apuntados.
Esta relajación debe ser cuidadosa para no crear un caos. En programación orientada a objetos la forma controlada de combinar varios tipos de objetos de denomina polimorfismo y consiste en tolerar que a un objeto de un cierto tipo T1 se le asigne otro objeto de otro tipo T2, con la restricción de que T2 sea un descendiente (tipo derivado) de T1. En particular, esta relajación se puede aplicar a los punteros:
TYPE
PT1= ^T1;
T1= OBJECT ... END;
PT2= ^T2;
T2= OBJECT (T1) ... END;
VAR
O1: PT1;
O2: PT2;
PROCEDURE P (X: PT1);
BEGIN ... END;
BEGIN
O1:= O2; (* polimorfismo legal *)
P (O2); (* polimorfismo legal *)
Esta tolerancia tiene cierta lógica. Un tipo derivado sólo puede ser un enriquecimiento de otro tipo. Los datos de un ascendiente son un subconjunto de los datos de un descendiente, y por tanto todo lo que el antecesor conoce puede localizarse en el descendiente. Pero, aparte de no dejar ningún vacio, un descendiente puede tener un diferente concepto de lo que hace un cierto método (un refinamiento): existe siempre algo que se llama igual, pero su ejecución puede ser diferente. Estos métodos son los que se denominan virtuales:
T1= OBJECT
PROCEDURE X; virtual;
END;
T2= OBJECT (T1)
PROCEDURE X; virtual;
END;
T1.X y T2.X pueden tener implementaciones distintas.
Un puntero a un objeto de tipo PT1, puede apuntar a un objeto de tipo T1, en cuyo caso O1^.X ejecutará T1.X. Pero también puede encontrarse apuntando a un objeto de tipo T2, en cuyo caso ejecutaría T2.X. Sólo se sabe en tiempo de ejecución a qué está apuntando en cada momento.
Estos procedimientos requieren una atención especial, razón por la cual se le informa al sistema adosándoles la palabra VIRTUAL.
Los objetos susceptibles de ser apuntados se encuentran con otros detalles operacionales relativos a su inicialización y a su liquidación (destrucción). Nos referimos a las operaciones de ubicación de memoria (NEW) y de liberación (DISPOSE).
La ubicación de memoria requiere al menos dos fases: (1) ubicar sitio y (2) inicializar los datos. Como un objeto suele tener datos privados sólo accesibles a través de los métodos que aporta públicamente (al menos esto es lo que recomiendan los tratados de buenas maneras de programar), ocurre que tendremos algún método que se denomine "carga" o similar. Por ejemplo:
T1= OBJECT
PROCEDURE X; virtual;
CONSTRUCTOR init (valor: tipo);
PRIVATE
dato: tipo;
END;
CONSTRUCTOR T1.init (valor)
BEGIN
dato:= valor;
END;
Y la creación de un objeto de este
tipo viene en dos fases:
NEW (O1); O1^.init (...);
que, por comodidad y por sistematizar operaciones, se pueden combinar en una
sóla operación:
NEW (O1, init (...));
Aparte de las razones aducidas de comodidad, en el caso de Turbo Pascal el sistema requiere que si un objeto tiene métodos virtuales, antes de utilizar estos se haya utilizado algún constructor. Esto es pura manía de Turbo Pascal; pero si no se hace, los programas no funcionan.
La operación simétrica, DISPOSE sufre una evolución similar:
T1= OBJECT
PROCEDURE X; virtual;
CONSTRUCTOR init (valor: tipo);
DESTRUCTOR final;
PRIVATE
dato: tipo;
END;
DESTRUCTOR T1.final;
BEGIN
(* lo que haga falta ... *)
END;
Donde "lo que haga falta" quiere decir que antes de eliminar un objeto puede ser necesario atender a otras facetas del programa. Casos típicos:
Sea por la razón que sea, podemos
eliminar en una o dos operaciones:
O1^.final; DISPOSE (O1);
DISPOSE (O1, final);
Cuando trabajamos con métodos vrituales, además de la problemática expuesta nos encontramos con situaciones en las que no se sabe hasta tiempo de ejecución que es lo que hay que destruir. Esto lleva simplemente a que los métodos destructores sean de tipo VIRTUAL.
Los métodos virtuales nos llevan a la posibilidad de escribir unidades que trabajan sobre cualquier tipo de datos dentro de una jerarquización de objetos derivados. La técnica consiste en identificar un tipo muy básico con las mínimas funciones necesarias, que se etiquetan como virtuales. Sobre este tipo elemental se desarrollan algoritmos que hacen referencia a los métodos virtuales. Estas unidades se pueden cerrar sin necesidad de que se conozca exactamente a que descendientes del tipo básico se van a aplicar.
Más tarde, se pueden derivar nuevos objetos a partir del tipo básico, dando contenido diverso a los métodos virtuales que quedaron abiertos. Se monta todo junto y en tiempo de ejecución los algoritmos genéricos que habiamos codificado se materializan sobre las instancias precisas de los objetos derivados.
Esta posibilidad de escribir unidades genéricas lleva a una posibilidad práctica de disponer código reutilizable o incluso comercializable por separado.