Вы находитесь на странице: 1из 9

El juego de los Animales

En este clásico juego que se ha utilizado desde años y con versiones para los primeros BASIC, se pregunta
para adivinar de qué animal se trata haciendo una serie de preguntas. Si el programa no adivina de qué
animal se trata, pregunta de manera que pueda aprender para juegos futuros,
En el comienzo , el juego solo conoce el perro, así que hasta que aprenda un poco más, será un poco
aburrido. También hará preguntas para distinguir cada nuevo animal de los anteriores cargados en su base
de datos.
El original de esto es uno de los innumerables tutoriales de distintos Smalltalk que hay en Internet.
Se acompaña el dibujo del original.
Podemos reconocer que es un árbol invertido con su raíz en la parte superior . Los nodos Question
(Pregunta) representan las ramas y las hojas son los nodos Animal,
Elegir una de los animales mostrados (Dog, Snake, Eagle , Horse) (Perro, Serpiente, Aguila, Caballo) como
intentos de acierto y siga el +arbol de decisión desde la raiz.
Parece entonces que podemos construir nuestra base de conocimientos con dos clases de objetos, Animales
y Preguntas.
También debemos guardar el objeto raíz ( una Pregunta) en alguna parte para poder comenzar el juego.

Herencia de Clases
Ahora , agregaremos la clase Animal a nuestra imagen. Repasemos un poco la herencia de clases.
Busquemos por ejemplo en el System Browser la clase Form que hemos usado. Con la clase seleccionada,
aquellos afortunados que tienen más de un botón, pulsen botón derecho o h o expand hierarchy y les
mostrará un Class Hierarchy Browser.
El Browser mostrará alguna de las clases que ya existen en la imagen. Esta jerarquía es importante dado que
gobierna como una clase en particular “hereda” la funcionalidad de sus clases padres. Eligiendo un lugar
apropiado en la cadena de herencia para definir nuevas clase nos ahorrará tiempo n no escribirá código que
ya esta hecho (reusabilidad).
Clases y Superclases
Recordamos algunas definiciones. Vemos que una clase puede tener varias clase hijas. Estas se conocen
como subclases. La clase padre se conoce como Superclase

Relaciones Es un y Contiene un
Volvemos a nuestra clase Animal. Donde en la jerarquía de clases la creamos?. Lo primero a considerar es
que clase de información contiene. Mirando el diagrama vemos que cada animal conoce su nombre, esto es
que clase de animal es. Esta información estará en forma de una cadena de testo String en Smalltalk:
Ejemplo ‘Perro’. `Gato’, ‘Caballo’. Así una posible ubicación sería en la clase String
Si buscamos la clase String nos encontraremos lo siguiente

Veremos métodos que realmente no nos son útiles, como los primeros de la lista . En realidad , si pensamos
un poco , veremos que Animal no “Es un” String , sino que “Contiene un” String. Esta es una decisión
fundamental que los diseñadores de software hacen cuando hacen "object oriented analysis" sobre un
problema de diseño. A menudo el problema es lo suficientemente complejo como para dibujar un diagrama
del modelo de objetos que ilustre las relaciones entre los varios objetos en el sistema. Esta relaciones se
describen como Es un y Contiene un.
Entonces , ya que Animal contiene un String, es mejor que sea una subclase de Object.

Creando una clase


Ahora creemos una clase como subclase de Object.
Recordar, todo lo que hagamos estará en una categoría Ejercicios, según ya vimos.
En el panel correspondiente debe decir
Object subclass: #Animal
instanceVariableNames: 'nombre'
classVariableNames: ''
poolDictionaries: ''
Aceptar esto.

Agregando métodos de acceso.


Una de las cosas sobre la programación orientada a objetos en general y de Smalltalk en
particular es que no se tiene acceso a los datos guardados dentro del objeto excepto que haya
métodos para eso escritos en la clase. Por ejemplo podemos guardar y obtener el valor de la
variable de instancia nombre.
Para hacer esto agregamos dos mensajes , nombre y nombre:
Este tipo de mensajes se conoce como mensajes de acceso o accessor methods
nombre
“ Devuelve el nombre del receptor ”
^nombre
Agregar lo anterior a la lista de mensajes, creando una categoría de mensajes denominada acceso
nombre:unaCadena
“Privado – inicializa el nombre del receptor a unaCadena”
nombre := unaCadena
Agregar lo anterior a la lista de mensajes.
Este mensaje recibe un parámetro denominado unaCadena. Los nombres de las clases, categorías, mensajes
son importantes. Tratar siempre de elegir buenos nombres que den al próximo que lea nuestro código una
idea de que hace nuestro código al leerlo. Hacer buenos comentarios
Probando nuestro código
Lo bueno del Smalltalk es que inmediatamente podemos probar nuestro código.
Creamos una instancia de Animal en el Workspace
miAnimal _ Animal new.
Ejecutar (do it)
Ahora escribir
miAnimal nombre
Remarcar y pulsar botón derecho print it. (Siempre que de adelante en los tutoriales diga mostrar esto es lo
que deben hacer) Debe aparecer nil al lado de lo escrito
Agregar miAnimal nombre: ‘Perro’ ejecutar y remarcando la línea anterior mostrar
Nil
Cuando en el ejemplo , luego de crear la instancia pedimos mostrar su nombre, vimos como resultado nil,
Esto es un valor especial que Smalltalk usa para indicar que la variable no ha sido inicializada.
Nil tiene otros usos aparte de ver si una variable esta o no inicializada, por ejemplo para representar el
concepto de algo indefinido

Mensajes de creación de instancias


Por defecto, todas las clases responden al mensaje #new con una nueva instancia de la clase
Sin embargo, usar #new para crear una nueva instancia de Animal no es ideal ya que finalizamos con un
animal sin nombre. Sería mejor asegurarse que cada instancia sea creada con un valor String válido.
Podemos hacerlo agregando mensajes de creación de instancias que tomen como parámetro el nombre del
animal. Este tipo de mensaje no es aplicable a la instancia de Anima sino a la clase. Por esta razón se
denomina class method en vez de los mensajes de instancia que usamos hasta ahora.
Para crear un mensaje de clase, fijarse que esté seleccionado de clases en el browser
En el panel de creación de categorías . elegir en el menú contextual new category y se desplegará el
siguiente menú .

elegir instance creation, (creación de instancias)

Representando un objeto como texto


Hay muchas situaciones en que deseamos mostrar nuestro objeto como texto. Por convención Smalltalk
provee mensajes definidos en Objeto para este propósito.
Agreguemos al Workspace y mostremos
miAnimal printString
Nos informó el nombre de la clase (anAnimal)
Agreguemos una categoría a la clase animal denominada mostrar (displaying) y los siguientes mensaje a la
misma
displayOn: aStream
"Answer the name of the receiver prepended by 'a' or 'an' as appropriate"

| aOrAn |
aOrAn := nombre first isVowel ifTrue: ['an '] ifFalse: ['a '].
aStream nextPutAll: aOrAn; nextPutAll: self nombre.
printOn: aStream
"Answer the name of the receiver as a developer would wish to see it."
super printOn: aStream.
aStream nextPut: $(.
self displayOn: aStream.
aStream nextPut: $)
.Los mensajes #printString and #printOn:están definidos en la clase Object, de la cual Animal hereda. Lo
que acabamos de hacer se denomina overriding y reemplaza el mensaje de la clase por el mensaje de la
instancia
Agreguiemos nuevamente miAnimal printString al Workspace y mostremos.

Self y Super
En el último mensaje agregamos el uso de estos dos importantes conceptos.
Si en un mensaje, deseamos enviar otro mensaje al objeto receptor, entonces usamos la palabra especial self
para representar el objeto. Miremos el mensaje #printOn y veamos como trabaja. Queremos utilizar
#displayOn para imprimir el nombre del animal como parte de la representación total,
Lo hacemos mandando el mensaje #displayOn a self.
Un concepto mas complicado es el de super. Lo utilizamos cuando deseamos mandar un mensaje al receptor
pero no en ninguna declaración local (esto es en la clase ). Cuando utilizamos super , el sistema comienza la
búsqueda del mensaje a ejecutar , no en la clase del receptor sino en la superclase. Es frecuente querer
heredar la funcionalidad pero agregar funcionamiento, esto es lo que se hizo con #printOn.

Creando la clase Pregunta


Vimos anteriormente que el juego de los animales probablemente requiere dos clases. Ahora crearemos la
clase Pregunta.
Que conocemos del comportamiento de Pregunta?. Observando el diagrama de la estructura de datos para
un juego típico, vemos que Pregunta también necesita contener algún texto con la pregunta que el sistema
debe hacer. Aparte de esto, cada objeto Pregunta necesita indicar cual rama del árbol seguir según se
conteste Sí o No a la pregunta. Parece entonces que necesitamos tres variables, texto, si y no. También
parece que la superclase sea nuevamente un Object.

Agregando la clase Pregunta como subclase de Object.


Siguiendo los pasos para agregar clases al sistema.
Object subclass: #Pregunta
instanceVariableNames: 'texto si no'
classVariableNames: ''
poolDictionaries: ''

Agregando los mensajes de acceso


Agregar los siguientes mensajes para obtener los valores de las tres variables de instancia.
si
“ Devuelve la rama en el árbol de conocimiento para tomarlo como resultado de la pregunta
al receptor”
^si
no
“ Devuelve la rama en el árbol de conocimiento para tomarlo como resultado de la pregunta
al receptor”
^no
texto
“Devuelve la pregunta del receptor como una cadena de texto”
^ texto
Ciertamente es útil acceder a los valores de estas variables separadamente. Sin embargo , una vez que se ha
creado una instancia de Pregunta, no necesitamos darle valores a estas variables nuevamente.
Por tanto, tiene sentido utilizar un mensaje privado para dar valores a todas las variables de una vez durante
la creación de instancia. Usaremos para esto el siguiente mensaje.
texto: unaCadena si: caminoSi no: caminoNo
“ Privado – Coloca el valor unaCadena al receptor texto, el valor caminoSi al receptor si y el
valor caminoNo al receptor no. Con un pequeño truco, al final el receptor cambia su
identidad con la de caminoNo ”
texto := unaCadena.
si := caminiSi.
no := self.
self become: caminoNo.

Agregamos estos mensajes en la categoría acceso.

Agregando un mensaje de creación de instancia.


Ahora necesitamos un mensaje de clase cuando creamos una instancia de Pregunta
Asegurarse por tanto , de que el browser muestre que está listo para clases, de otro modo podemos borrar lo
que acabamos de agregar del lado de las instancias.

texto: unaCadena si: caminoSi no: caminoNo


“Devuelve una instancia del receptor (una Pregunta) con unaCadena dirigiendo hacia los
nodos de caminoSi y caminoNo como resultado de la pregunta”
^self new texto: unaCadena si: caminoSi no: caminoNo

Este mensaje se agrega a la categoría creación de instancias


Creando una base de conocimiento que contenga la base de conocimiento.
Antes mencionamos que necesitamos guardar la raíz de la base de conocimiento en una variable .
Podríamos utilizar una variable global para este propósito. Sin embargo, es mejor evitar esto para evitar
conflicto con nombres similares en otra aplicación. La alternativa es utilizar una variable de clase.
Vimos como se pueden utilizar las variables de instancia para los datos. También es posible crear un tipo de
variable asociada con cada objeto particular en la clase.
Seleccionamos la clase Animal y disponemos el browser para clases. Modificar y aceptar el cambio
Por convención las variables de clase comienzan con Mayúsculas.
Esta nueva variable solamente se podrá usar dentro del alcance de la clase Animal. Esto significa que se
puede utilizar en cualquier mensaje que esté definido para Animal o sus subclases.
No se puede referenciar la variable KnowledgeBase desde otra clase o desde el workspace. Esto significa
que no se puede referenciar desde Pregunta. Este nivel de privacidad comparado al de las verdaderas
globales significa que otro programa o clase no la pueden modificar por error.
Inicializando la clase Animal.
Inicializamos KnowledgeBase de manera de obtener un objeto adecuado en la raíz del juego al comienzo.
Si agregamos esta inicialización como parte de los mensajes de clase luego podemos reusar cuando
queremos reinicial el juego (olvidar lo aprendido). Así agregamos el siguiente mensaje de clase a Animal en
la categoría inicializar.

inicialize
“Inicializar la base de conocimiento del juego de los Animales .
Al comienzo solamente conocemos el perro “
KnowledgeBase:= self nombre:= ‘Perro‘

Ahora en el workspace, evaluar lo siguiente de manera de realizar la inicialización.


Animal inicialize
La inclusión de un mensaje #inicialize tiene un significado adicional cuando grabamos nuestro trabajo para
ser utilizado por otro. Cuando se agrega una clase en la imagen, el sistema chequea para ver si existe un
mensaje #inicialize para ejecutarlo automáticamente en caso afirmativo. Como se ve, esta es una buena
forma de asegurarse que quedan adecuadamente seteadas las variables de clase.

Completando el juego
Ahora solo nos faltan pocos mensajes que terminarán de colocar todas las piezas juntas. Veamos un poco de
la forma en que encontraremos el código escrito en la nomenclatura inglesa.

Animal>>thisMethod
Indicates that thisMethod belongs on the instance side of the Animal class
Animal class>>thisMethod
Indicates that thisMethod belongs on the class side of the Animal class
Question>>thisMethod
Indicates that thisMethod belongs on the instance side of the Question class
Question>>thisMethod
Indicates that thisMethod belongs on the instance side of the Question class

El símbolo >> se utiliza como forma de documentar que el mensaje (método) pertenece a una clase
particular.
Haciendo preguntas
Cuando el juego funciona, la computadora nos hará preguntas. A medida que el juego recorra el árbol de
conocimiento en un momento en particular, estará en un nodo Pregunta o si le parece que conoce que
animal es , en un nodo Animal. Si repasamos el diagrama, vemos que todo el tiempo nos preguntará.
“Tiene alas ?” Para un objeto Pregunta o
“Estás pensando en un Águila?” para un objeto Animal.
Lo importante ahora es ver que podemos escribir el código del juego de tal manera de independizarnos del
tipo de nodo. Podemos hacerlo enviando un mensaje #ask a cada nodo e implementando un método
adecuado #ask en cada clase Animal y Pregunta. Este comportamiento es típico de la programación
orientada a objetos y se denomina Polimorfismo.
Así que agreguemos nuestros métodos polimórficos #ask a Pregunta y Animal. Colocarlos en la categoría
operaciones.
ask
| repuesta pregunta miAnimal |
pregunta := 'El animal es que piensa ',self texto.
repuesta _ FillInTheBlank request: pregunta.
repuesta = 'SI' ifTrue: [miAnimal _ self si.
repuesta _ 'El animal era ', miAnimal nombre.
PopUpMenu inform: repuesta
]
ifFalse: [miAnimal _ self no.
miAnimal ask]

Ahora el método Animal>>ask . Cuando es llamado, la computadora piensa que tiene una repuesta pare el
animal, así que pregunta si la repuesta es correcta, y si es así, gana el juego. Si no, pregunta por un nuevo
Animal para crearlo con la repuesta correcta y para una nueva Pregunta que sea distinta de la original.
Veremos que hay algunos nuevos mensajes que se envían para los que no hemos codificado métodos, pero
los haremos posteriormente.

Animal>>ask
ask
| repuesta pregunta newAnimal newQuestion |
pregunta := 'El animal es que piensa es ', self nombre.
repuesta _ FillInTheBlank request: pregunta.
repuesta ='SI' ifTrue: [PopUpMenu inform: 'Acerte '].
repuesta ='NO' ifTrue: [newAnimal := Animal prompt ].
newQuestion := Pregunta preguntarParaDiferenciar: self from: newAnimal.
pregunta := 'Desea continuar jugando ? '.
repuesta _ FillInTheBlank request: pregunta.
repuesta ='SI' ifTrue: [Animal playGame ].
Preguntando por un nuevo Animal
Vemos en el último método una necesidad de crear un nuevo Animal preguntando por su
nombre.
Animal class>>prompt
prompt
"Prompt for a new animal to add to the game"
| newName |
[newName isNil or: [newName isEmpty]] whileTrue: [
newName _ FillInTheBlank request: 'En que animal estaba pensando ?'.].
^self nombre: newName

Como vemos, el método usa una clase llamada Prompter, la que se utiliza para pedir al usuario una cadena
de texto. Esta se chequea para validez y se utiliza para crear una instancia de Animal utilizando nuestro
método #nombre . dado que el método #prompt pregunta por una nueva instancia, tiene sentido colocarlo en
la categoría creación de instancias. Recordar que es un método de clase, así que utilizar el browser
seleccionado para clases.
Ingresando una nueva Pregunta
Y finalmente , necesitamos un método para un nuevo objeto Pregunta.

Pregunta class>> preguntarParaDiferenciar


preguntarParaDiferenciar: anOldAnimal from: aNewAnimal
| viejo nuevo questionText pregunta |
viejo _ anOldAnimal nombre.
nuevo _ aNewAnimal nombre.
pregunta := 'Ahora por favor ingrese una pregunta que se responda por SI para ',nuevo.
pregunta := pregunta, ' Y por NO para '.
pregunta := pregunta,viejo.
[questionText isNil or: [questionText isEmpty]] whileTrue: [
questionText _ FillInTheBlank request: pregunta.
].
^self texto: questionText si: aNewAnimal no: anOldAnimal

Ahora a jugar
Estamos lista para agregar un último método para nuestro juego de los animales. Necesitamos un método
sencillo para comenzar el juego.

Animal class>>playGame
playGame
PopUpMenu inform: 'Piense un animal y yo intentare adivinar'.
KnowledgeBase ask.

Hacer fileIn de Ejercicios.st y de Workspace.text.


Divertirse y analizar lo que hace el programa

Вам также может понравиться