Este video es un Redo para el MOOC.
Re-do en el sentido que le pedimos de rehacer, de
seguir el video y paso a paso repetirlo.
También es una invitación a mirar cómo programamos.
No vamos a seguir un guión preciso,
Intentaré hacerlo, pero si encuentro errores o
cosas así, voy a manejarlos a medida que avance.
En este video, la idea es programar un pequeño
lenguaje que puede encontrar en juegos de rol como
"Calabozos y Dragones", por ejemplo,
esta expresión
¿Qué significa esta expresión?
Significa que debe lanzar 2 dados de 20 lados y 1 dado de 4 lados.
Verá que como és una expresión en Pharo.
Entonces en este video vamos a
implementar una clase que representa un dado y una clase
que representa una mano.
Empezaremos.
Comenzamos definiendo un paquete.
A este paquete lo llamamos "Dice" (dado).
No tengo ganas de ver
mas cosas. Entonces este
video no lo codificaré en el
depurador, como ha visto en otro video, lo haré
caso a caso de manera oportunista.
Aquí defino la clase "Die", que tiene
una cierta cantidad de "faces" (caras).
Compilo y agrego un comentario de clase.
Por el momento, poca cosa, porque es muy muy simple.
Empezaremos de manera que podamos
inicializar nuestro objeto
Lo haré así.
Llamo la inicialización de la superclase, y
por defecto asigno 6 caras a mi dado, porque es
el dado más común.
Ahora estoy empezando a desarrollar una
clase de test, para asegurarnos de que lo que hagamos
no rompe lo que ya ha esta hecho.
Las clases de prueba son subclases del clase TestCase
Lo llamamos "DieTest".
Tengo mi clase de test (prueba).
Una de las primeras pruebas que haremos,
no siempre tenemos que hacerlo así, pero
en este caso me gustaría comenzar con una prueba que funcione.
Como aquí, no tenemos mucha cosa, dire que la inicialización está ok.
Esta es también una manera de mostrarte
cómo probar que puedes capturar excepciones
o que las excepciones no deben ocurrir.
Aquí estoy diciendo:
Die new should not raise error.
Qué significa eso?
Significa que cuando ejecuto este trozo de código "Die
new" (dado nuevo), no debe producirse ningún error.
Voy a clasificar mi test y ejecutarlo.
Verde. Todo bien.
Ahora, me gustaría definir el
método que imita una tirada del dado.
Me parece que en Pharo debe haber un método
llamado "atRandom" (al azar).
atRandom, ¿qué hace?
Me permite tener ... Ok ...
Entonces ahora voy a mirar la implementación para asegurarme
que es correcto. AtRandom, ¿qué hace?
Devuelve un número entero al azar De 1 a sí mismo, perfecto.
Entonces voy a definir un nuevo
método en Operations (operaciones).
Que va a hacer
Roll (rueda). Le diré me devuelves "faces
atRandom" (caras al azar).
Entonces escribo un test para esto.
TestRolling ¿Que hacemos ahora?
Creamos un dado. d := Die
new
Y ahora escribo "1000 timesRepeat".
¿Qué? "d roll".
Y quiero que sea entre 1 y 6
"Between: and:", esta
bien. (Entre 1 y 6).
No es muy bueno porque aquí creamos
una prueba solo para dados de 6 lados, podríamos haber hecho que
funcione según el número de lados del dado
Lo haremos más tarde.
Entonces compilo
Me sale un error Aquí está ...
Está bien, tengo mi test.
Ahora es el momento de guardar. Aquí tengo mi
paquete "Dice", lo guardo localmente, "Save".
Había creado unos cuantos antes para entrenar, así que
volveré a crear uno "New version with
rolling and test". Muy bien.
Ok, se guarda.
Ahora me gustaría cambiar
la interfaz de creación Primero re-categorizamos
Si queremos cambiar un poco la interfaz de creación
Nos gustaría decir: "to create a die
use die faces".
En esta expresión debe ver que
faces és un mensaje enviado a la clase Die y no
a una instancia de la clase Die, como en el
método roll o otros métodos codificados con anterioridad.
Haré esto para que entienda cuando
tiene ir o no al nivel class.
Empezaremos escribiendo un test.
Así "betterInterface".
Si continúo con la misma lógica,
"TestbetterCreationInterface",
Me gustaría hacer algo como esto,
y que esto, sea faces (caras).
Lo haré lentamente.
Vengo aquí y escribo "instance creation" faces: anInteger.
Podría escribirlo de una manera compacta pero aquí
lo haré de manera tranquila.
Así creo un dado.
Escribo "self new", self es la clase Die en sí.
Le pido: crea una instancia.
Y ahora a esta nueva instancia le proporciono
un acceso para facilitar el valor pasado como argumento
Obviamente, devuelvo el dado que acaba de ser creado.
Cuando ejecute el código, no funcionará porque
faces no existe, no se preocupe.
Vemos que mi test no pasa, en todo caso
es normal si yo ejecuto esto,
por ejemplo, si hago debug (depuración) para ver ...
Y hago clic en Over, aquí dice "no conozco
el mensaje faces".
Aquí lo haremos con calma, no lo haré en el débugger (depurador).
Yo digo: es verdad, tengo que agregar un descriptor de acceso
Entonces escribo "faces: anInteger"
Y allí escribo: "faces: anInteger"
Y mientras estoy en ello, creo el acceso de lectura.
Devuelvo esta.
Y aquí mi test es verde.
Entonces guardamos, "save"
mejor die creación método con test.
Todo bien.
Ahora podemos comenzar a
definir lo que queremos para
diceHandle. Básicamente si miramos, diceHandle,
¿Cómo nos gustaría escribirlo?
Nos gustaría escribir "diceHandle new addDie"
Aquí crearemos un nuevo dado, "Die faces: 6, addDie".
"die faces: 10".
Comenzamos escribiendo
una clase de test, esta vez.
Entonces, una nueva clase que hereda de TestCase.
Todo bien. Tengo mi nueva clase de test
Y defino un test.
La idea es crear una mano y luego
verificar que tengo los. dados buenos dentro.
Escribo "testAdding", quiero reutilizar mi
código, no hay otro motivo.
Entonces tengo mi
handle, yourself porque quiero
obtener el receptor del mensaje, es decir la mano
y no el argumento que está aquí.
Ahora, ¿qué debo hacer?
Escribo "self assert: h diceNumber
equals: 2".
Compilo. Obviamente el sistema me dice: "No
conozco la variable DiceHandle, quieres
que sea una clase"
Sí. Debe ser una clase.
Aquí la definirá.
Como se que de todos modos tengo que parar mis dados,
aprovechare para
poner una variable de instancia
Compilo todo esto
Ahora es rojo porque "AddDie:" no se ha definido nunca.
Entonces lo haremos.
Antes de hacer esto, estaría bien de inicializar la mano,
lo hacemos así, nos evitará
tener un error más tarde
"dice : = OrdereCollection new"
Re-categorizar.
Y ahora, debo ser capaz de ejecutar mi
test, que se bloqueará. Ok muy bien.
Crearé AddDie:
Agregando
Aquí, me dice: "Debes implementar este método."
Sí, tiene sentido.
Escribo "Dice add: aDie".
Ok muy bien. Mi prueba continua sin funcionar
porque aun no he definido el método
diceNumber. Sí, diceNumber,
lo crearemos, en accessing esta vez
Y diceNumber, ¿que va a hacer?
Debe devolver
dice size. Compilo de nuevo proceed.
Y mi prueba debe ser verde Entonces, la prueba
es verde y guardo.
"With addDie and test".
Podríamos mejorar el test porque aquí
comprueba que agregamos 2 números, me gustaría
verificar que cuando
agregamos dos veces el mismo dado no lo perdamos.
Escribo "TestAddingTwiceTheSameDieIsOK".
Aquí que haré
Agrego 6 y 6 y debo obtener 2
Hago esto, corro mi prueba, es verde, súper
Ahora, seria bonito poder
definir como
agregar 2 dados. Pero antes, hagamos algo.
Si miras, lo que no me gusta,
es que cuando inspecciono esto, por ejemplo,
si hago "Inspect" aquí, no veo
los valores de los dados y no es práctico para depurar.
En el depurador, no vemos esto.
Entonces, antes de continuar, quiero mejorar
esto. Voy a agregar un método
en el protocolo printing. El método "PrintOn"
esta definida en todos los objetos del sistema y
convertirá un objeto en una
representación textual y
pasa un stream al que incorporaremos
la representación que queramos dentro.
Si hago esto, no he hecho nada.
Si hago super PrintOn, de hecho tengo, no he hecho nada.
Ahora voy a hacer
"aStream NextPutAll:", entonces pondré caracteres en el
stream, pero ¿qué voy a poner primero?
Escribiré un paréntesis con un espacio tal vez
sea mas bonito, un paréntesis.
Entonces consideraré caras (faces) y las convertiré
en números, en cadenas y concatenare todo esto
con un paréntesis de cierre.
Si hago esto ... cerré el depurador, entonces lo abro de nuevo.
Tengo el depurador. Ahora Tengo un dado de 6 caras
y un dado de 10 caras. Así es mucho
más bonito, verá, si encuentro errores, nos ayudará.
Aqui, no hice nada especial, mis test funcionan.
Guardo de nuevo, no cuesta nada, "With printing".
Escribimos "with die printOn".
Muy bien. Ahora creamos el test,
no lo haremos, iremos directamente allí
Haremos "add en el protocolo roll operations".
Aqui,
hay varias formas de definir esto.
Le propongo una, no es la mas bonita pero
al menos es la que probablemente sea mas clara para usted.
Hay maneras muy compactas, lo
podría escribir en una linea, pero utilizando iteradores
como "Injected to", aquí, estoy realizaré un bucle
¿Entonces qué hago?
Tomo un valor que inicializo a cero.
Luego hago un bucle en todos los dados, y
a cada paso del bucle recupero un dado y
¿Que voy a hacer con este dado?
Le pido que realice una tirada y agregue el resultado a
mi variable
Nada muy especial pero al menos es muy muy explícito
Ahora si hago "Inspect" y luego
hago "Roll", 5, esto no prueba que funcione.
Intentemos-lo una vez más.
11. Muy bien, funciona.
Intentaremos escribir un test, no hay motivo,
entonces haremos "Test", quiero guardarlo
y lo llamo rolling.
Entonces, ¿cómo podríamos probar esto?
Tendría que ser entre 1 y el máximo numero de dados.
Entonces haremos esto.
Nosotros
podríamos definir un método que haga esto.
Hagamos un método que defina el máximo.
operations "maxValue".
¿Qué es maxValue?
Finalmente. Está muy cerca de esto.
Aquí en lugar de hacer roll,
voy a pedir las caras (faces).
Comprobemos. Si hago "Inspect", aqui
voy a hacer "maxValue 16".
Sí, es solo 10 y 6.
Entonces escribimos un test para
"maxValue". Lo tengo, Hago "maxValue equal:
16".
Entonces, puede ver, que podría haber codificado algo
muy feo en mi test, pero finalmente es mejor
crear un método en la clase y utilizarlo.
Entonces, podemos probar que el método roll
funciona bien. Hagamos "roll",
y le pido que debe estar comprendido
entre ... "Roll between: 1 and:
h maxValue.
Si hago esto, ok, funciona.
No es muy estadístico, así que aquí podríamos realizar
algo como 1000 timesRepeat.
De acuerdo. Y aquí,
tenemos nuestras 1000 pruebas.
Guardamos. Muy bien.
Guardamos de nuevo. save, added maxValue
and roll with tests.
Casi hemos terminado,
nos gustaría expresar que
en lugar de tener "die faces: 6",
nos gustaría tener "1 D6".
Y finalmente,
lo que ves significa "enviar
el mensaje D6 a un entero.
Entonces vamos a la la clase Integer
Lo que vamos a hacer es
definir una extensión de la clase.
¿Qué es una extensión de la clase?
Voy a empacar mis métodos con el mismo nombre de mi paquete.
Entonces, ¿vera qué hago? Agrego un
protocolo, le pongo *, debe empezar por *dice que es
el nombre de mi paquete, automáticamente esto se pone gris lo que
significa que el método será empaquetado
al mismo tiempo que este paquete. Hagamos-lo.
Imaginemos que hacemos ... ¿Qué es D6?
Un D6 ... tengo que pensar un poco sobre eso ...
Primero creamos una mano porque esto podría
ser dos D6 finalmente.
Entonces "handle", hacemos "DiceHandle
new",
ok, entonces he creado mi truco.
Ahora para cada receptor, voy a hacer
"self", este es mi entero, "TimesRepeat".
Habremos utilizado mucho el timesRepeat, es raro.
"TimesRepeat handle addDie:", ¿de qué?
de "die faces". Y ahí,
lo sabemos, es 6
Y de hecho, tal vez sea para devolver la mano.
¿Es que funciona?
Lo probamos así y escribimos un test.
Si hago 2 D6, inspect, Mira,
tengo 2 D6. Eso es genial.
Pues escribamos un test.
Clasificamos esas pruebas después de todo.
Escribimos "testNewSyntax".
Aquí por el momento solo tenemos D6, generalizaremos más tarde.
Queremos hacer exactamente lo mismo
que esto, entonces tendremos una mano, digamos 2 D6.
Y allí haremos "selfAssert".
¿Qué podemos probar?
Que diceNumber equals: 2,
por ejemplo. Puedes ver que podria haber usado
diceHandle = 2, aquí.
En general, es mejor usar "assert: equal:"
porque así, cuando hay un error, el sistema
te muestra "He recibido esto y obtuve este valor en lugar de ... "
Si escribo =, dirá: "Tengo una expresión equivocada".
Aquí para el usuario final, que es usted,
como desarrollador, es mejor usar
"assert equals" porque le dira: "He recibido 3, cuando
esperaba 2", por ejemplo.
Entonces aquí, si hago esto.
Ok, funciona.
¿Podríamos tener una versión
mas inteligente para este test?
De momento, este nos conviene.
Ahora puede ver que
en el paquete, tengo una extensión llamada D6.
Generalizaremos esto con "a number until",
"a number of faces"
Asi "aNumberOfFaces", lo pondremos aqui.
Y reescribimos D6 porque es mucho mejor D6
Escribimos D6 de esta manera.
haremos "^ self D: 6".
Haremos todos los demás.
Haremos 4,
2. Es más una moneda que un dado, pero 2,
10 y 20.
¿Ha entendido el principio?.
Entonces, probamos los test, puesto que hemos cambiado la implementación.
2 D6 ... significa que funciona.
Entonces guardemos.
¿Qué nos queda por hacer?
De hecho, nos falta poder sumar manos.
¿Qué test me gustaría tener?
Me gustaría que si hago, por ejemplo,
"AddingHandles", asegurarme que
Puedo utilizar la nueva sintaxis.
Muy bien.
Me gustaría que si
Escribo 2 D20 + 3 D6
no vamos a complicar las cosas
cosas. ¿Cuánto debo obtener?
diceNumber debe ser igual a 5.
Aquí ves que debemos definir el operador +
En Pharo + no es un operador, es solo un mensaje.
Entonces definimos un mensaje en la clase DiceHandle
Escribimos +.
Entonces "aDiceHandle".
Ahora podemos preguntarnos si
modificamos el receptor o si utilizamos
un enfoque funcional
Prefiero usar un enfoque funcional.
en el que creamos una nueva mano.
Entonces voy a crear una nueva mano, escribo
"handle := self class new".
Evité escribir diceHandle y más tarde
hay una lección que le explicara porque.
Lo prefiero, es mucho mas cercano.
En general, no codifica a palo el nombre de las clases.
Lo verá en la semana 7,
hay toda una explicación.
Si hago "self dice do:", lo que haré
es iterar con mis dados y agregarlos a una mano.
Así que hago "handle addDie: each",
y hago lo mismo ... Aquí no
necesito self y de hecho no conoce
el mensaje, eso es lo que quería decir y
me hace notar que, de hecho, no lo he definido
y no ha funcionado para "diceHandle", pero
no es grave, compilamos y ya lo solucionaremos más tarde.
Entonces, ¿qué significa?
Significa que le falta un acceso que es dice.
Entonces agregamos dice aquí, dice que me devuelve
la colección de mis dados
Ahora voy a probar, a ver si mi test funciona.
Mi test funciona, es súper significa que prácticamente
he acabado, guardo, "with handles
additions". Ok, muy bien.
Significa que ahora podemos escribir
2 D4 y luego
hacer "Roll", lo que nos devolverá un número.
Ahora está listo para jugar a "Calabozos y Dragones".
Lo que debe retener, hemos definido
nuestro métodos, hemos definido nuestros test, los hemos hecho
rodar, extendimos una clase del sistema,
la clase Integer, con extensiones vinculadas a nuestro
paquete, que solo serán visibles cuando nuestro paquete se cargue.
También sobrecargamos a los operadores, pero
de hecho, solo definimos un nuevo mensaje +, porque
en Pharo la adición es solo otro mensaje
esto nos permitió expresar con cierta facilidad un bonito DSL.
¡Ahora os toca a vosotros!