Enviando Mensajes a Objetos
Los Objetos son instancias de una clase en particular. Los mensajes a los que un objeto puede responder están definidos en el protocolo de su clase. El cómo estos mensajes se ejecutan o son implementados está definido en los métodos de la clase. Los métodos dan los detalles de implementación para los mensajes, y representan el comportamiento de la clase.
Dando nombre a Objetos y Mensajes, todos los nombres de clases comienzan con una letra en mayúscula, como por ejemplo «Alumno«. Los nombres de Mensajes comienzan con una letra en minúscula y pueden tener cualquier combinación de letras y números sin espacios en blanco. Cuando el nombre de un mensaje contiene más de una palabra, las palabras extras comienzan con una letra en mayúscula. Un nombre válido de mensaje podría ser «unMensaje» o «MiDirección.»
Existen tres tipos de mensajes: unarios, binarios y de palabra clave.
Mensajes Unarios
Un mensaje Unario es similar a la llamada de una función con parámetro único. Este tipo de mensaje consiste de un nombre de mensaje y un operando. Los objetos se colocan antes del nombre del mensaje.
Mensajes Binarios
Los mensajes Binarios son utilizados para especificar operaciones aritméticas, lógicas y de comparación. Un mensaje binario puede ser de uno o dos caracteres de longitud y puede contener cualquier combinación de los siguientes caracteres especiales:
+ / \ * ~ < > = @ % | & ? ! ,
Mensajes de Palabra Clave
Un mensaje de Palabra Clave es equivalente a una llamada de un procedimiento con dos o más parámetros. Observe el siguiente ejemplo, el nombre del objeto, al cual el mensaje es enviado, se escribe primero, luego el nombre del mensaje (o nombre del método) y luego el parámetro que se pasa.
UnObjeto unMensaje: parametro
Los dos puntos (:) son una parte esencial del nombre del mensaje.
Cuando hay más de un parámetro, el nombre del mensaje debe aparecer para cada parámetro.
Formato de un Mensaje
En general, una expresión Smalltalk consiste en el nombre del objeto que recibe el mensaje, seguido por el nombre del mensaje.
Por ejemplo:
UnObjeto unMensaje
Para aquellos mensajes que necesitan pasar argumentos al objeto receptor, como los mensajes de palabra clave, al nombre del objeto receptor le sigue el nombre del mensaje y su argumento. Los argumentos se separan por medio de un espacio en blanco.
UnObjeto unNumero: 1 unNombre: 'Pedro'
Identificadores de Objetos
Cuando un objeto es creado, Smalltalk le asigna un identificador. Cada objeto tiene su propio y único identificador que tiene un significado sólo para el sistema Smalltalk.
Creando Nuevas Instancias
Para crear nuevas instancias de una clase, el mensaje new es enviado a la clase.
Por ejemplo:
Alumno new
Cada clase automáticamente contiene el mensaje new. El mensaje new devuelve un puntero a la nueva instancia.
En el siguiente ejemplo, la variable global MiAlumno apunta a un nuevo objeto Alumno:
MiAlumno := Alumno new
En Smalltalk, la expresión := es la sintaxis para la función de asignación. La variable de la izquierda apunta al resultado de la expresión de la derecha.
Una vez ejecutada la sentencia de arriba, los mensajes pueden ser enviados a MiAlumno, que apunta a una instancia del objeto Alumno.
Por ejemplo:
MiAlumno nombre: 'Luis' MiAlumno nombre
Concepto de self
Considere la siguiente sentencia:
MiAlumno nombre: 'Pedro' direccion: 'Medrano 951'
La sentencia envía el mensaje name:address: al objeto Alumno apuntado por la variable MiAlumno. En vez de duplicar el código que ya existe en los métodos nombre: y direccion:, el método nombre:direccion: se implementa ejecutando los métodos nombre: y direccion:. Dado que nombre: y direccion: son métodos del mismo objeto que nombre:direccion:, debe existir alguna forma para que un método pueda referirse al objeto en el cual existe.
Este es el propósito de self. Un método puede accesar otros métodos en su propio objeto especificando self como el receptor del mensaje.
nombre: unNombre direccion: unaDireccion
self name: unNombre.
self direccion: unaDireccion
El uso de self hace que Smalltalk envíe los mensajes nombre: y direccion: a la instancia actual de Alumno, la misma instancia que recibió el mensaje nombre:direccion:.
Orden de Ejecución de los Mensajes
Las reglas que usa Smalltalk en la decisión del orden de ejecución de los mensajes, puede resumirse en los siguientes pasos:
- Smalltalk ejecuta mensajes de izquierda a derecha.
- El resultado de un mensaje reemplaza al mensaje en la sentencia.
- Smalltalk ejecuta primero todas las expresiones que aparecen entre paréntesis, comenzando por la izquierda, y por aquella que está más anidada.
- Dentro de una expresión, los mensajes unarios se ejecutan primero, luego los mensajes binarios, y finalmente los mensajes de palabra clave; siempre de izquierda a derecha.
- Smalltalk ejecuta todos los mensajes binarios de izquierda a derecha, independientemente de las operaciones que realicen. Esto significa que no hay un orden especial para ejecutar operaciones aritméticas.
- Una expresión puede incluir el nombre de una variable. Smalltalk reemplaza el nombre de la variable con el objeto al que ella apunta.
Instrucciones
En Smalltalk, el código ejecutable está formado por instrucciones. Cada instrucción tendrá una o más expresiones Smalltalk. Una expresión válida de Smalltalk puede ser cualquiera de las siguientes:
- Un nombre de variable
- Un literal
- Un envío de mensajes
Cada instrucción se separa por un punto, excepto la última instrucción. Si una instrucción tiene más de una expresión, la instrucción se ejecuta de acuerdo al orden de ejecución de los mensajes. Múltiples instrucciones pueden aparecer en una sola línea; o una sola instrucción puede aparecer en muchas líneas.
Asignación
Una asignación cambia el valor del objeto al que apunta una variable. En Smalltalk, una variable apunta a un objeto en vez de guardarlo. Se dice que una variable contiene al identificador del objeto.
La sintaxis para una instrucción de asignación es:
variable := instrucción
donde la variable puede ser un nombre dentro del alcance del método, y la instrucción puede ser cualquier instrucción válida de Smalltalk.
Devolviendo un Valor
En Smalltalk, un mensaje siempre devolverá un valor. La devolución por defecto, es el receptor del objeto. Un método puede anular su valor de retorno por defecto, colocando un símbolo de intercalación (^) adelante de una instrucción. Cuando se encuentra un ^, el método finaliza la ejecución y devuelve el valor de la instrucción que le sigue al símbolo de retorno ^.
Por ejemplo:
^instrucción
Se retorna el resultado de la instrucción, donde instrucción puede ser cualquier instrucción válida.
Comentarios
En Smalltalk, los comentarios se encierran entre comillas dobles, tal como:
"esto es un comentario en Smalltalk"
Es una convención aceptada el tener comentarios al principio de un método, para explicar su comportamiento, incluyendo su valor de retorno.
Variables Temporales
Las variables temporales están definidas en la definición de interfase de un método. Un método puede tener más variables temporales, listándolas entre barras verticales (‘|’ pipeline),
Por ejemplo:
| nuevoNombre nuevaDireccion |
Esta instrucción no termina con un punto.
El nombre de una variable temporal comienza con una letra en minúscula, y debe ser único dentro del método. Esto significa que no puede duplicar el nombre de una variable de instancia, ni duplicar el de una variable temporal definida en una interfase, así como tampoco puede duplicar el nombre de una variable temporal cualquiera.
Estructura Básica de un Método
El siguiente es un ejemplo de un método:
unMetodoEjemplo: algunaEntrada
"Esto es un ejemplo de un método."
| nuevoValor |
nuevoValor := algunaEntrada * 2.
^nuevoValor
Este método no tiene mayor significado que el de ilustrar el aspecto básico de un método.
- La primera línea define la interfase del método, así como también al nombre del método. Este método es una método de palabra clave con un argumento. El nombre del método es unMétodoEjemplo y la variable temporal es algunaEntrada.
- La segunda línea es un comentario describiendo lo que hace el método.
- La tercera línea es una declaración de una variable temporal adicional llamada nuevoValor. Esta variable temporal es local sólo dentro de este método.
- La cuarta y la quinta línea contienen la lógica del método. Un punto es opcional para la última línea de un método. La última línea contiene una expresión de retorno, que no será necesaria a menos que se explicite un valor de retorno diferente al receptor del mensaje.
Sin embargo este método no contiene un código eficiente. Define una variable que mantiene el resultado de la multiplicación, y luego usa esa variable temporal sólo en la instrucción de retorno.
Bloques
Los bloques son corchetes que contienen ninguna o muchas expresiones, y un código que realiza iteraciones o ejecuciones condicionales.
Un bloque puede pensarse como un mini-método dentro de un método. Las siguientes reglas se aplican a los bloques:
- Un bloque puede contener cualquier número de instrucciones válidas ejecutables, o cualquier número de comentarios.
- Cada instrucción debe terminar con un punto, excepto cuando se definen las variables temporales, y en la última instrucción del bloque, donde el punto es opcional.
- Un bloque tiene acceso a las mismas variables que el método al que pertenece.
Como un bloque es parte de un método no posee una definición de interfase de método. El bloque en la siguiente instrucción:
ifTrue: [x := 2]
es llamado bloque de cero-argumento; no puede aceptar ningún argumento. Sin embargo es posible definir un bloque que pueda tomar argumentos, como el siguiente:
[:variable1 | código]
[:variable1 :variable2 | código]
donde variable1 y variable2 son variables temporales y son válidas sólo dentro del alcance del bloque. El nombre de la variable es precedido por dos puntos ‘:’.
Mensajes en cascada
Algunas veces es necesario enviar a un objeto varios mensajes consecutivos. Como ejemplo, revea el siguiente código:
Ejemplo 3.3: Mensajes en cascada
nombre: unNombre direccion: unaDireccion telefono: unTelefono
"Determina el nombre, dirección y teléfono de una instancia Alumno."
self nombre: unNombre.
self direccion: unaDireccion.
self telefono: unTelefono
Self es el receptor de los mensajes para todas las instrucciones del método. Para ejecutar los mensajes correctamente, necesitan aparecer en instrucciones separadas.
Existe una forma de acortar el código que permite trabajar con instrucciones consecutivas. La primera instrucción se escribe de manera normal, pero todas las instrucciones sucesivas pueden omitir al objeto receptor. Cada instrucción termina con un punto y coma (;) en vez de con un punto (.), exceptuando a la última instrucción para la que el punto es opcional. El método del Ejemplo 3.3 puede se escrito de la siguiente forma:
nombre: unNombre direccion: unaDireccion telefono: unTelefono
"Determina el nombre, dirección y teléfono de una instancia Alumno."
self nombre: unNombre;
direccion: unaDireccion;
telefono: unTelefono
La primera instrucción se escribe de manera normal, excepto que termina con un punto y coma. El objeto receptor del último mensaje en esta secuencia, se vuelve el objeto receptor del primer mensaje en la siguiente instrucción.
Encapsulando Variables de Instancia
Encapsulando las variables de instancia dentro de una clase, los cambios pueden ser hechos a partes específicas de la clase sin afectar otras partes de la clase. Estas variables se mantienen con la ayuda de dos métodos llamados en forma general: obtener y determinar. Estos métodos son la única forma de accesar las variables de instancia de una clase. Un método determinar existe para proveer una manera de cambiar el valor de una variable de instancia por un valor pasado como parámetro. El método obtener provee una forma de hacer que el receptor retorne el valor de una variable de instancia al objeto emisor.
Por ejemplo, el objeto Alumno tiene tres métodos obtener y determinar, uno por cada una de sus variables de instancia. Ellos son:
nombre "obtiene el valor de nombre" direccion "obtiene el valor de direccion" nombre:unNombre "determina nombre al valor unNombre" direccion:unaDireccion "determina direccion al valor unaDireccion"
Métodos adicionales pueden ser creados a partir de estos determinar y obtener para combinar sus capacidades: nombre:direccion: "Determina nombre y direccion en un mensaje"
Literales
Smalltalk provee soporte para los literales como instancias de cinco clases: String (cadena), Number (número), Character (caracter), Symbol (símbolo) y Array (vector). Las subclases de Number: Integer (entero), Float (real), y Fraction (fracción) son parte de los literales también.
Number
Un number (número) puede ser uno cualquiera de los siguientes:
- Un integer (entero) de cualquier longitud
- Una fraction (fracción)
- Un número de floating point (punto flotante)
.23 "UN ERROR! Un número no puede comenzar con un punto decimal" 59. "UN ERROR! Un número no puede terminar con un punto decimal" 2.4e.7 "UN ERROR! El exponente debe ser un entero"
Character
Un character (caracter) es cualquier caracter ASCII precedido por el signo $, tal como $G o $@.
String
Un string (cadena) es cualquier secuencia de caracteres encerrados entre comillas simples. Por ejemplo:
'Esto es una cadena.'
Symbol
Un symbol (símbolo) es un identificador, selector binario, o un selector de palabra clave, precedido por un signo numeral, #. Todos los símbolo son únicos.
#nombre "un identificador"
Array
Un array (vector) es una estructura de datos cuyos elementos pueden ser cualquier objeto válido. Un vector literal es una secuencia indexada de otros literales. Un vector se caracteriza por estar encerrado entre paréntesis y precedido por un signo numeral (#). Por ejemplo:
#(1 'dos' $Y) "Produce un vector con un entero, una cadena, y un caracter"
Operaciones Aritméticas
Una instrucción que realiza una operación aritmética en Smalltalk tiene la siguiente forma:
número operación número
donde una operación puede ser una de las siguientes:
+ "suma" - "resta" * "multiplicación" / "división" // "división entera (cociente)" \\ "resto de una división"
Concepto de Nil
En Smalltalk, nil es un objecto que significa «nada«. Inicialmente todas las variables apuntan a nil. Cualquier variable puede ser apuntada a nil durante la ejecución, con una instrucción como la siguiente:
UnaVariable := nil.
Nil también puede usarse como un valor de retorno para indicar que una operación no fue exitosa.
Comparaciones Lógicas
Una instrucción de comparación tiene el siguiente formato:
valor comparación valor
donde valor puede ser cualquier expresión que resulte en un valor que pueda ser comparado, tal como números, cadenas, caracteres, y símbolos; y comparación puede ser cualquier operación válida de comparación. Algunos ejemplos son:
> "mayor que" < "menor que" = "igual en valor" ~= "desigual en valor" >= "mayor o igual que" <= "menor o igual que" == "el mismo objeto que"
Las compraciones lógicas devuelven un valor que puede ser true (verdadero) o false (falso), que son instancias de las clases True y False, respectivamente.
«y» Lógico y «o» Lógico
En Smalltalk, las expresiones booleanas pueden ser combinadas en un resultado, usando la operación o, o la operación y. Estas dos funciones pueden ser utilizadas como mensajes binarios o mensajes de palabra clave.
Mensajes Binarios
El mensaje binario para el y lógico es &, y para el o lógico es |. Por ejemplo:
(a > 0) & (b < 0) "Devuelve true si a es positivo y b es negativo. Caso contrario devuelve false."
(a > 0) | (b < 0) "Devuelve true si a es positivo y/o b es negativo. Caso contrario devuelve false."
Mensajes de Palabra Clave
El mensaje de palabra clave para y lógico es and:, y para o lógico es or:. El formato para estos mensajes es:
booleano and: [código] booleano or: [código]
El booleano es cualquier expresión cuyo valor resulte true o false. El bloque de código encerrado entre corchetes (ver «Bloques«) debe devolver un valor de true o false. Los métodos and: y or: combinan los dos valores booleanos y devuelve el resultado correspondiente.
Existe una diferencia entre los mensajes binarios & y |, y los mensajes de palabra clave and: y or:, respectivamente. Los mensajes de palabra clave son considerados caminos cortos porque utilizan evaluación diferida. El código en el bloque no es evaluado hasta que el valor del receptor booleano no es determinado como true o false.
En el caso del mensaje and:, si el receptor evalúa false, entonces el código del bloque no se ejecuta, ya que en una operación and con un false siempre es false.
En el caso del mensaje or:, si el receptor evalúa true, entonces el código del bloque no se ejecuta, ya que en una operación or con un true siempre es true.
«o» Exclusivo
Un booleano también soporta la función de o exclusivo proveyendo el mensaje de palabra clave xor:. Este es idéntico en formato a los mensajes de palabra clave and: y or:, excepto que el argumento debe ser otro booleano, y no un bloque de código.
Not
El mensaje unario not provee la función not. Este mensaje invierte un valor booleano (true se vuelve false, o false se vuelve true). El formato es:
booleano not
Lógica Condicional
La lógica condicional permite la ejecución del código dependiendo de un valor booleano. Existen varios mensajes de palabra clave que proveen esta función, por ejemplo:
booleano ifTrue: [código] ifFalse: [código].
donde booleano es cualquier expresión que resulte true o false. La expresión [código] puede ser cualquier bloque cero-argumento.
El mensaje de palabra clave ifTrue:ifFalse ejecuta un bloque diferente dependiendo del valor del booleano. Por ejemplo:
| x y nuevoValor | x := 1. y := 2. (x > y)
ifTrue: [nuevoValor := x]
ifFalse: [nuevoValor := y].^nuevoValor
Este ejemplo determina el valor de la variable nuevoValor al valor del mayor entre x o y, en este caso y, y devuelve el valor en nuevoValor.
Iterando
Smalltalk soporta cuatro tipos tradicionales de iteraciones. Ellos son:
- Hacer algo n número de veces
- Hacer algo hasta que se encuentre con una condición false
- Hacer algo hasta que se encuentre con una condición true
- Hacer algo usando un índice, comenzando con un valor inicial, y finalizando en un valor final.
Los cuatro mensajes de palabra clave que proveen estas funciones son: timesRepeat:, whileTrue:, whileFalse:, y to:do:.
timesRepeat:
El mensaje timesRepeat: ejecuta un bloque de código un número específico de veces. El formato del mensaje es:
número timesRepeat: [código]
donde número puede ser cualquier expresión que resulte en un entero, y código es un bloque de código de cero-argumeto.
whileTrue: y whileFalse:
Estos dos mensajes realizan la misma operación, excepto que uno se ejecuta por true y el otro por false. El formato del mensaje es:
[booleano] whileFalse: [código] [booleano] whileTrue: [código]
Un booleano puede ser cualquier expresión que resulte en un valor de true o false; debe estar encerrado en un bloque. La expresión [código] es un bloque de código de cero-argumento.
to:do:
El mensaje to:do: ejecuta un bloque múltiples veces, basado en un valor inicial y un valor final. El formato del mensaje es:
número1 to: número2 do: [:var | código].
donde número1 y número2 pueden ser cualquier expresión que resulte en un número, y [:var | código] es un bloque de código de un-argumento. El bloque se ejecuta para cada número perteneciente al rango entre número1 y número2, inclusive. (Este formato es utilizado generalmente con enteros, que varían en el rango de a 1 a la vez.). El argumento del bloque de un-argumento equivale al valor actual en el rango.