Este documento describe los conceptos básicos de los lenguajes de programación orientados a objetos, enfocándose en Smalltalk. Explica que los objetos son instancias de clases y que las clases definen los métodos y variables de instancia. También describe cómo funciona la herencia, los tipos de datos y la implementación de Smalltalk a través de objetos.
1. Lenguajes
de
programación
Abdiel E. Cáceres González
Instituto Tecnológico de Monterrey
Campus Ciudad de México
Verano 2004
Tower of Babel by Gustav Dore 1986
1
2. Métodos
Un método de una clase define las operaciones que una instancia de la clase
ejecutará cuando se reciba un mensaje correspondiente a ese método. En cierto
sentido, los métodos son como las definiciones de funciones, incluyendo el uso de
parámetros y la capacidad de regresar valores.
2
3. Métodos
La forma sintáctica general de un método en Smalltalk es:
patrón_del_mensaje[ |variables temporales| ] sentencias
donde los corchetes son meta-símbolos que indican que lo que se encuentra dentro
de ellos es opcional.
3
4. Métodos
Dado que Smalltalk no tiene declaraciones de tipo, las variables temporales, cuando
están presentes, sólo necesitan ser nombradas en una lista. Las variables temporales
existen sólo durante la ejecución del método en el cual están listadas. No hay signos
de puntuación al final de un método.
El mensaje del patrón corresponde a las sentencias procedurales de lenguajes
imperativos tales como Pascal. Los patrones de mensajes, los cuales son prototipos
para los mensajes, pueden estar en una de dos formas básicas.
4
5. Métodos
Para los mensajes unarios o binarios, sólo se incluye el nombre del método. Para los
mensajes de palabra clave, se incluyen las palabras clave y los nombres de los
parámetros del patrón de mensaje.
Para indicar que un método regresará un valor, se utiliza la flecha hacia arriba (^). En
muchos casos, esta es la última expresión que aparece en el método. Si no se
especifica un valor de retorno en un método, el objeto receptor mismo es el valor de
retorno.
5
6. Clases
Todos los objetos de Smalltalk son instancias de clases. Una clase tiene cuatro
partes:
1) Un nombre (de la clase).
2) El nombre de la superclase, el cual especifica la posición de la nueva clase en la
jerarquía de clases del sistema.
3) Una declaración de las variables locales, llamadas variables instanciadas. Estas
declaraciones estarán disponibles a las instancias de la clase.
4) Los métodos que definen cómo responderán las instancias de la clase a los
mensajes. (Recordemos que un objeto hereda también el método de todas las clases
que son sus ancestros).
6
7. Clases
Los mensajes a un objeto normalmente hacen que se busque un método
correspondiente en la clase a la que el objeto pertenece. Si la búsqueda falla, se
continúa ésta en la superclase de esa clase y así sucesivamente, hasta llegar a la clase
del sistema, llamada “Object”, la cual no tiene superclase.
Si no se encuentra un método en ninguna parte de esa cadena, entonces ocurre un
error. Es importante recordar que este método de búsqueda es dinámico.
7
8. Clases
Ejemplo de una definición de clase:
"Smalltalk Example Program"
"The following is a class definition, instantiations
of which can draw equilateral polygons of any
number of sides"
class name Polygon
superclass Object
instance variable names ourPen
numSides
sideLength
"Class methods"
"Create an instance"
new
^ super new getPen
8
9. Clases
"Get a pen for drawing polygons"
getPen
ourPen ← Pen new
"Instance methods"
"Draw a polygon"
draw
numSides timesRepeat: [ourPen go: sideLength;
turn: 360 // numSides]
"Set length of sides"
length: len
sideLength len
"Set number of sides"
sides: num
numSides num
9
10. Clases
Smalltalk sigue el Principio de Abstracción al agrupar los objetos que tienen
comportamiento y propiedades similares bajo la misma clase. Nótese que
simultáneamente, los particulares que distinguen un objeto de otro se omiten.
Este enfoque resulta ideal para simulación y es una muestra de la fuerte influencia
de Simula-67 en Smalltalk.
La memoria privada de una instancia de una clase contiene sus variables
instanciadas.
Las variables instanciadas no son visibles a otros objetos. Cada variable instanciada
se refiere a un objeto, al que se denomina “valor”. Los valores de todas las instancias
de una cierta variable representan (en su conjunto) el estado actual de esa instancia.
10
11. Clases
Las variables instanciadas pueden ser “nombradas” o “indizadas”.
Las variables nombradas corresponden a apuntadores a tipos distintos de los
arreglos en un lenguaje imperativo.
Las variables indizadas no se accesan mediante su nombre, sino mediante los
mensajes que usan parámetros enteros.
La mayor parte de las variables indizadas se usan de tal forma que corresponden a
arreglos en lenguajes imperativos convencionales, aunque la indización misma se
realiza a través del paso de mensajes.
11
12. Clases
El parámetro entero en un mensaje se usa para referenciar una variable indizada
instanciada y corresponde al subíndice de un arreglo en un lenguaje imperativo
convencional.
Las instancias de una clase se crean enviando el mensaje “new” a la clase en una
asignación, la cual hace que la variable a su izquierda haga referencia al nuevo objeto
recién creado.
Por ejemplo:
ourPen ← Pen new
crea una instancia de la clase “Pen” (enviando el mensaje “new” a la clase “Pen”).
12
13. Clases
Esto también hace que la variable “ourPen” haga referencia a esta nueva instancia
de la clase “Pen”.
Este ejemplo demuestra que pueden enviarse mensajes tanto a las clases como a
los objetos.
Las variables de Smalltalk no tienen tipo; cualquier nombre puede asociarse con
cualquier objeto.
El único chequeo de tipos que se efectúa ocurre dinámicamente cuando un mensaje
se envía a un objeto.
13
14. Chequeo de Tipos
Si el objeto es una de sus clases previas (ancestro), entonces tiene un método para el
mensaje, el mensaje es legal y el objeto reacciona a él. Si no hay método que
corresponda a un mensaje, ya sea en el objeto en una de sus clases ancestrales,
entonces se produce un error en tiempo de ejecución.
Este es un concepto de chequeo de tipos significativamente diferente del utilizado
en los lenguajes imperativos. El chequeo de tipos en Smalltalk tiene como objetivo
simplemente asegurar que un mensaje corresponde a un cierto método.
Advierta que el chequeo dinámico de tipos no implica en este caso un chequeo débil
de tipos. Smalltalk tiene un chequeo fuerte de tipos, a pesar de que éste es dinámico
(en vez del chequeo estático de lenguajes como Pascal y Ada).
14
15. Chequeo de Tipos
Una ventaja del chequeo dinámico de tipos es su flexibilidad, ya que podemos hacer
el mismo trabajo que con los paquetes genéricos de Ada, pero sin recurrir a la
inherente complejidad de esa técnica.
Puesto que Smalltalk es un lenguaje interpretado y no compilado, el activar mensajes
en tiempo de ejecución es algo aceptable y la motivación para detectar tan temprano
como sea posible un error que se aplica a los lenguajes compilados no tiene razón de
ser en este caso.
Asimismo, no hay violaciones al Principio de Seguridad, porque Smalltalk sigue
checando si un objeto tiene un método que responda a un cierto mensaje y, de no
encontrarlo, activa un error. De tal forma, se impide que el programa se colapse
debido a violaciones al sistema de tipos.
15
16. Herencia
La herencia es naturalmente un concepto central en Smalltalk. El sistema de
Smalltalk incluye una enorme jerarquía de clases. La programación en Smalltalk
consiste, en gran medida en crear subclases de las clases existentes.
Es precisamente este reuso potencial de código existente lo que resulta más
interesante y valioso sobre el mecanismo de herencia. De hecho, la herencia es
quizás la contribución más importante de los lenguajes orientados a objetos.
16
17. Herencia
Las subclases pueden construirse basadas en el comportamiento de sus superclases,
pero también pueden modificarlo. Recordemos que en los lenguajes estructurados,
una redeclaración de una variable anula la declaración previa.
Lo mismo ocurre en Smalltalk con los métodos: la definición de un método en una
subclase anula cualquier definición que pueda existir de ese mismo método en sus
superclases.
Un objeto puede enviarse un mensaje a sí mismo porque es un miembro de su
superclase. Como puede verse, Smalltalk es un sistema muy extensible, en el cual el
reuso de software tiene un significado real.
17
18. Herencia
Un problema con las jerarquías definidas en Smalltalk es que su organización impide
el uso de una clasificación ortogonal. En la vida real, frecuentemente nos
encontramos con objetos que deben ser clasificados de varias formas diferentes.
Por ejemplo, un biólogo podría clasificar a los mamíferos como primates, roedores,
rumiantes, etc. Alguien interesado en los usos de los mamíferos podría clasificarlos
como mascotas, bestias de carga, fuentes de comida, etc. Finalmente, un zoológico
podría clasificarlos como Norteamericanos, Sudamericanos, Africanos, etc.
18
19. Herencia
Estas son 3 clasificaciones ortogonales: cada una de las clases corta a través de las
otras en ángulo recto. En Smalltalk, el programador está forzado a violar ya sea el
Principio de Seguridad o el de Abstracción, a fin de modelar una clasificación
ortogonal.
Es decir, tenemos que repetir atributos de algunos objetos en otras clases (violando
el Principio de Abstracción) o tenemos que efectuar una clasificación anidada de
clases (violando el Principio de Seguridad). La fuente del problema es que un objeto
puede pertenecer a sólo una clase a la vez.
Cuando se define una clase, puede especificarse que será una subclase inmediata de
exactamente una clase (y sólo una). Una solución posible a este problema que sigue
siendo tema de investigación es permitir la herencia múltiple.
19
20. Implementación:
Clases y Objetos
Others Smalltalk
Una de las cosas más interesantes de Smalltalk es
que la mayor parte de su sistema está escrito en
3%
Smalltalk.
Esto incluye el compilador, el de-compilador, el
depurador, los editores y el sistema de archivos.
En total, esto constituye alrededor del 97% del
97%
código de Smalltalk.
20
21. Implementación:
Clases y Objetos
Una de las razones principales por las cuales Smalltalk puede programarse en
Smalltalk es que la mayor parte de las estructuras de datos de la implementación,
tales como los registros de activación del lenguaje, son objetos de Smalltalk.
Esto significa que pueden ser manipulados mediante programas en Smalltalk y que
tienen las propiedades de los objetos de Smalltalk (por ejemplo, se auto-despliegan).
La parte de Smalltalk que no es portátil es llamada “Máquina Virtual de
Smalltalk-80”; su tamaño va de 6 a 12K en ensamblador. Los diseñadores de
Smalltalk aseguran que toma alrededor de un año producir una versión
completamente depurada de una “Máquina Virtual de Smalltalk”. Esto
hace de Smalltalk un excelente ejemplo del Principio de Portabilidad.
21
22. Implementación:
Clases y Objetos
La Máquina Virtual de Smalltalk tiene 3 componentes principales:
1) Manejador de Almacenamiento: Es el manejador de tipos de datos abstractos para
los objetos. Encapsula la representación de los objetos y la organización de la
memoria.
Las únicas operaciones que otros módulos pueden realizar sobre los objetos son
aquellas proporcionadas por el Manejador de Almacenamiento (Storage Manager):
1) Tomar la clase de un objeto.
2) Tomar y almacenar los campos de los objetos.
3) Crear nuevos objetos.
22
23. Implementación:
Clases y Objetos
Puesto que el Manejador de Almacenamiento tiene que ser capaz de crear nuevos
objetos, debe poder obtener espacio libre donde colocarlos, también. Por lo tanto,
otra responsabilidad del Manejador de Almacenamiento es colectar y manejar el
espacio libre de memoria.
Para este propósito, usa una estrategia de conteo de referencias con extensiones
para poder manejar estructuras cíclicas.
23
24. Implementación:
Clases y Objetos
2) Intérprete: Es el corazón del sistema de Smalltalk.
Aunque sería posible interpretar directamente las formas escritas en Smalltalk, es
más eficiente si el intérprete opera sobre una forma intermedia del programa. El
intérprete es esencialmente el manejador de tipos de datos abstractos para los
métodos.
24
25. Implementación:
Clases y Objetos
Subrutinas Primitivas: Es simplemente una colección de los métodos que, por
razones de desempeño, se implementan en lenguaje máquina en vez de hacerse en
Smalltalk. Aquí se incluyen las funciones básicas de entrada/salida, la aritmética con
enteros, el indizamiento de los objetos que lo requieren (p.ej., los arreglos) y las
operaciones gráficas básicas.
La representación de un objeto debe contener sólo la información que varía de
objeto a objeto. La información que sea igual para una clase de objetos se almacena
en la representación de dicha clase.
25
26. Representación de Objetos
La información que varía entre las instancias de una clase es simplemente la
correspondiente a las variables de instanciación. La información almacenada con la
clase incluye los métodos de la clase y los métodos de la instancia.
Puesto que necesitamos saber la clase a la que pertenece cada objeto, la
representación de un objeto debe contener alguna indicación de su clase
correspondiente.
Hay varias formas de hacer eso: la forma más simple es incluir en la representación
del objeto un apuntador a la estructura de datos que representa la clase.
26
27. Representación de Objetos
B1
box
len. 6 B2 len. 6
c.d. c.d.
loc loc
500@600
tilt tilt
size size
scribe scribe
len. 4 point
c.d.
500
x
y 200
27
28. Representación de Objetos
Además de las variables de instanciación y de la descripción de la clase, cada objeto
tiene un campo de longitud requerido por el manejador de almacenamiento para
asignar, liberar y mover objetos en memoria.
Puesto que el Smalltalk mantiene la regularidad en todo momento, no debiera
sorprendernos que las clases sean también objetos, los cuales son instanciaciones de
la metaclase.
Por lo tanto, las clases se representan en la misma forma que los objetos, pero con
un apuntador a la metaclase. Las variables instanciadas de una clase contienen
apuntadores a los objetos que representan la información que es igual para todas las
instancias de la clase.
28
29. Representación de la clase Object
class
box class
string
len 8 “box”
c.d.
class
name
displayObject
superclass
string
inst.vars
“loc tilt size scribe”
class msgs.
ist. msgs.
messageDictionary
inst. size 4
message dict.
messageDictionary
message dict.
29
30. Representación de Clases
Esta información incluye lo siguiente:
El nombre de la clase
La superclase (la cual es “object” si no se especifica otra)
Los nombres de las variables instanciadas
El diccionario de mensajes instanciados
Advierta que en este caso es necesario conocer el número de variables instanciadas
para determinar la cantidad de almacenamiento requerida. Si no tuviésemos este
campo, sería más lento efectuar instanciaciones de objetos.
30
31. Representación de Clases
Puesto que sería demasiado lento que el intérprete tuviese que decodificar el
formato fuente de un método cada vez que el método fuese ejecutado, es una buena
idea compilar los métodos en alguna forma de pseudo-código que pueda
interpretarse rápidamente.
Por otra parte, el formato fuente de los métodos se requiere para editar y desplegar
las definiciones de la clase. Por lo tanto, los diccionarios de mensajes contienen dos
entradas para cada formato del mensaje: una que contiene el formato fuente del
método y otra que contiene una forma compilada.
Para encontrar estas entradas en el diccionario de mensajes, se utilizan técnicas de
“hashing”.
31
32. Representación de los
Registros de Activación
El paso de mensajes en Smalltalk es muy similar a la invocación de procedimientos
en los lenguajes estructurados. Por lo tanto, no debiera sorprendernos que se usen
técnicas de implementación muy similares, aunque con algunas diferencias
importantes.
Hemos visto que los registros de activación (RAs) son el vehículo principal para la
implementación de procedimientos. Puesto que un RA tiene toda la información
pertinente a una activación del procedimiento, el proceso de invocar un
procedimiento y regresar de él puede entenderse como la manipulación de RAs.
Lo mismo aplica a Smalltalk: usaremos registros de activación para conservar toda la
información relevante a una activación de un método.
32
33. Representación de los
Registros de Activación
La estructura de un RA en Smalltalk es muy similar a la usada en los lenguajes
estructurados:
1) Parte del ambiente: El contexto a usarse para la ejecución del método.
2) Parte de la instrucción: La instrucción a ejecutarse cuando este método
continúa.
3) Parte del remitente: El registro de activación del método que envió el mensaje
invocando este método (esto es similar a la liga dinámica).
33
34. Representación de los
Registros de Activación
Se usa un sistema de coordenadas en dos dimensiones para identificar instrucciones:
Un apuntador al objeto identifica el método-objeto que contiene todas las
instrucciones de un método.
Un desplazamiento relativo que identifica la instrucción en particular dentro del
par método-object
Este direccionamiento en coordenadas bidimensionales es necesario porque el
direccionamiento de instrucciones pasa a través del manejador de almacenamiento.
Esto último indica que nos adherimos al Principio de Ocultamiento de Información.
La parte del ambiente debe proporcionar acceso tanto al ambiente local como al no
local.
34
35. Representación de los
Registros de Activación
El ambiente local incluye espacio para los parámetros del método y para las variables
temporales. Esta parte del registro de activación debe proporcionar espacio para
variables temporales ocultas tales como los resultados intermedios de las
expresiones.
El ambiente no local incluye a todas las otras variables visibles (es decir, las variables
instanciadas y las variables de la clase). Las variables instanciadas se almacenan en la
representación del objeto que recibió el mensaje. Por lo tanto, un simple apuntador
a este objeto las vuelve accesibles.
35
36. Representación de los
Registros de Activación instance
Activation record
parameters length
locals temporary vars. c.d.
intermediates
non locals static link instance vars.
method
instruction part
instruction
sender part dynamic link
Class
Superclass
length length
c.d. c.d.
superclass superclass
class class
variables variables
36
37. Representación de los
Registros de Activación
La representación del objeto contiene un apuntador a la clase de la cual es una
instancia el objeto; por lo tanto, las variables de la clase también son accesibles vía la
referencia al objeto. Finalmente, puesto que la representación de la clase también
contiene un apuntador a su superclase, las variables de la superclase son también
accesibles.
Advierta que el enfoque usado por Smalltalk para acceder ambientes no locales es
muy similar a la cadena estática que estudiamos anteriormente. En esa técnica, la
cadena estática nos llevaba del ambiente activo más anidado al ambiente activo más
exterior.
En el caso de Smalltalk, la cadena estática nos lleva del método activo al objeto que
recibió el mensaje, y de ahí hacia arriba, a través de la jerarquía de clases, hasta
terminar en la clase “object” (la metaclase).
37
38. Representación de los
Registros de Activación
Al igual que en los lenguajes estructurados, el acceso a las variables requiere conocer
la distancia estática a la variable, de manera que se salte ese número de ligas de la
cadena estática para llegar al ambiente que define la variable.
38
39. Paso y Regreso de Mensajes
Cuando se envía un mensaje a un objeto, deben efectuarse los siguientes pasos:
1) Crear un registro de activación para el receptor (“callee”, en inglés).
2) Identificar el método que está siendo invocado extrayendo el patrón
(template) del mensaje y buscándolo en el diccionario de mensajes de la clase del
objeto receptor o en sus superclases.
3) Transmitir los parámetros al registro de activación del receptor.
4) Suspender al remitente (invocador) almacenando su estado en su registro de
activación
5) Establecer una ruta (liga dinámica) del receptor al remitente, y establecer
como activo el registro de activación del receptor.
39
40. Paso y Regreso de Mensajes
primerArreglo at: 1 put: 5
1 3
primerArreglo
parameters
locals temporary vars. 2 4
intermediates
non locals static link
instruction part
method 5
instruction
sender part dynamic link
40
41. Paso y Regreso de Mensajes
Como es de esperarse, regresar de un método debe revertir este proceso:
1) Transmitir el objeto retornado (si hay alguno) del receptor al remitente
2) Continuar la ejecución del remitente, restaurando su estado a partir de su
registro de activación
Advierta que los registros de activación no son liberados de la memoria
explícitamente, sino que se usa recolección de basura. El Manejador de
Almacenamiento crea el espacio libre y reclama espacio que un objeto ya no está
usando de vez en cuando.
Este enfoque es muy diferente del usado en los lenguajes estructurados, en los cuales
los registros de activación ocupan posiciones contiguas en una pila. El enfoque de
Smalltalk es menos eficiente, pero es más regular y más simple.
La otra razón para no usar una pila tiene que ver con la concurrencia. Las pilas sólo
son apropiadas si estamos lidiando con algo que siga una estructura LIFO (Last-In,
First-Out).
41
42. Paso y Regreso de Mensajes
Los procedimientos en un lenguaje procedural siguen esta estructura, pero los
procedimientos en un lenguaje concurrente no. Por lo tanto, necesitamos un modelo
diferente para lidiar con las tareas concurrentes en Smalltalk.
42
43. Puntos Finales sobre Smalltalk
Smalltalk es un buen ejemplo de lo que puede hacerse con un lenguaje pequeño,
simple y extremadamente regular.
Smalltalk ha sido utilizado en un sinnúmero de aplicaciones: juegos, simulaciones,
gráficos, inteligencia artificial, etc.
43
44. Puntos Finales sobre Smalltalk
Dado que Smalltalk no tiene tipos, hablar de polimorfismo no es muy adecuado,
pero sí podemos mencionar que la sobrecarga se da de manera natural. No hay
ninguna razón por la que objetos de diferentes clases no puedan responder a un
mismo mensaje.
El diseño de Smalltalk ha sido guiado por varios principios muy valiosos:
Simplicidad, Regularidad, Abstracción, Seguridad y Ocultamiento de Información.
En general, ilustra un principio de MacLennan que no habíamos mencionado: el
de la Elegancia.
44
45. Extensiones
Orientadas a Objetos
Varios lenguajes han sido extendidos para soportar el paradigma de orientación a
objetos. Por ejemplo: LISP, C y Ada-83.
Ada 95 posee nuevos mecanismos que hacen más flexible su manejo de clases y
tipos. De hecho, se manejan clases como tipos especiales (restringidos) que
pueden pasarse como parámetros pero no pueden usarse como tipos de variables.
Sin embargo, el soporte de orientación a objetos en Ada es realmente muy complejo
y costoso. Esto se debe a que se pretende agregar un nuevo mecanismo que implica
tipos dinámicos sin sacrificar la estructura de un sistema de tipos estáticos.
45
46. Extensiones
Orientadas a Objetos
Bjarne Stroupstrup
C++ es una extensión orientada a objetos del popular lenguaje C. C++ fue
desarrollado también en Laboratorios Bell y es, en gran parte, responsabilidad de
Bjarne Stroupstrup.
Las clases en C++ son una generalización del los registros (records) de C. C++
soporta objetos representados internamente a través de la declaración de clases.
Nótese, sin embargo, que C++ permite herencia múltiple. Esto se debe, en buena
medida, a que usa un sistema de tipos estático.
46
47. Extensiones
Orientadas a Objetos
Los componentes de una clase en C++ pueden ser de tipo “public” (visibles a todo
mundo), “protected” (visibles en la clase y sus subclases) o “private” (visibles sólo
dentro de la clase). La programación orientada a objetos de C++ depende en buena
medida de las funciones virtuales.
C++ se vale de los “templates” para implementar un mecanismo similar a los
paquetes genéricos de Ada.
La mayor crítica al C++ se debe a que retiene las características estáticas (de los
tipos, sobre todo) de C a fin de poder compilar ambos lenguajes.
47
48. Extensiones
Orientadas a Objetos
Java ha sido foco de atención más recientemente y puede verse como una variante
del C++. En Java se han enfatizado aspectos de seguridad, robustez, portabilidad y
asociaciones tardías. El enfoque principal del lenguaje es la programación en
ambientes de red y sistemas distribuidos.
Java omite varias funciones de C++, como por ejemplo sobrecarga de operadores,
apuntadores, coerciones excesivas y herencia múltiple (sólo se permite de manera
más restringida), pero a cambio agrega otras tales como recolección de basura
automática y un chequeo seguro de tipos.
48
49. Extensiones
Orientadas a Objetos
CLOS (Common LISP Object System) es una extensión bastante popular del
LISP que permite que éste soporte orientación a objetos. CLOS proporciona
objetos representados externamente dado que los métodos se implementan como
funciones genéricas. Dado que LISP tiene tipos dinámicos, éstos se mantienen en
la orientación a objetos.
Un detalle interesante de CLOS es que sí soporta herencia múltiple, pero el
mecanismo para realizarla dista de lo trivial. Las reglas de la herencia múltiple
deben expresarse en la forma de un algoritmo a fin de determinar la lista de
precedencia de clases que se requiere.
49