Hoy continuaremos con la sesión anterior
donde expliqué que los métodos pequeños eran buenos.
Hoy mostraré algunos ejemplos.
Pero primero, repasemos rápidamente nuestra sesión anterior.
Enviar un mensaje lleva a una elección.
Una opción enmascarada porque se invocarán varias
posibles implementaciones.
Y Pharo hace una elección dependiendo del receptor del mensaje.
La jerarquía de clases define las opciones: Más clases = más opciones.
Y puedes agregar fácilmente nuevas elecciones mediante la creación de subclases.
Las subclases pueden redefinir o refinar el código de su superclase.
Y enviar un mensaje
significa dar a las subclases la posibilidad de cambiar el comportamiento.
Hoy miraremos el patrón de diseño "Template Method".
Lo encontrarás en el manual de diseño de patrones.
¿Qué es un template method?
Es un esqueleto que define el comportamiento general
de un algoritmo, por ejemplo, con ganchos en el interior.
Y estos ganchos pueden ser redefinidos por subclases.
Este es un algoritmo.
Hace algo, no sabemos exactamente qué.
Luego, hace algo en hookMethod1,
luego algo más,
y luego algo en hookMethod2.
Dadp que hookMethods 1 y 2 son ambos métodos completos,
pueden ser redefinidos en las subclases.
Hay 2 posibilidades, uno para cada uno de hookMethod 1 y 2.
Estos métodos pueden o no
tener un comportamiento predeterminado.
Aquí, digamos que hookMethod1 tiene un comportamiento predeterminado.
Esto significa que si la subclase no propone nada,
hookMethod1 tiene un comportamiento predeterminado que funciona.
Del mismo modo, podríamos decir
ese hookMethod2 no tiene un comportamiento predeterminado.
La subclase debe imperativamente definir uno.
Como diseñador de la clase, puedes elegir
determinar o no un comportamiento predeterminado.
Usaremos printString como primer ejemplo.
La idea es que si envío el mensaje printString a un objeto,
obtengo un string que representa este objeto.
Aquí tengo un Delay.
He creado uno de 10 segundos.
Si le envío el mensaje printString,
obtengo aDelay y entre paréntesis, un milisegundo de valor para el mismo.
El método printString está implementa en la clase Object.
Envía el mensaje printStringLimitedTo:.
Esta implementación
recupera una string que representa
al objeto.
Y si este string es demasiado largo,
podemos cortarlo en un cierto límite.
Luego al final, Concatenamos con '... etc ...'
para avisar que no está completo.
Lo importante aquí
es que printStringLimitedTo: envía un mensaje printOn: a self.
Este es el método
que será redefinido o no en las subclases.
Si miro lo que devuelve printString
para un nodo y para una manzana,
Node new devuelve aNode.
Este es el printString de la clase Node.
Y aquí en Apple, tenemos el printString de la clase Apple.
Vemos que este comportamiento y el comportamiento predeterminado
se implementan en la clase Object.
Entonces, el comportamiento predeterminado de printString para cualquier objeto que sea
es, primero:
Recupera los nombres de clase.
En este caso, Node y Apple.
Y segundo, si el nombre de la clase comienza con una vocal,
utilizamos el prefijo "an".
si comienza con una consonante, utilizamos el prefijo "a".
Así es como obtenemos "aNode" y "anApple".
Este es el comportamiento predeterminado.
Pero es posible cambiarlo
refinando printOn:.
Para una instancia de Delay,
vemos que el printString
empieza con lo predeterminado.
Esto es "aDelay".
Pero luego, podemos agregar el tiempo en milisegundos entre paréntesis.
Esto es exactamente lo que el método printOn.
Comienza preguntando a la superclase por el printString predeterminado.
Después, abre un paréntesis
con un número predeterminado de milisegundos,
y luego cierra los paréntesis.
Solo miramos el refinamiento.
La clase Delay refina la implementación
del método printOn en la clase Object.
Pero una clase también puede redefinir completamente el comportamiento.
Este es el caso de los booleanos, por ejemplo.
Si muestro false,
devolverá false.
No tenemos un "a false",
simplemente "false".
Para esto, solo enviamos el string "false"
en printOn:.
Eso es una redefinición completa.
Otro ejemplo de redefinición completa es intervalos.
Un intervalo define un conjunto de valores
situado entre un valor mínimo y máximo.
El intervalo de 1 a 100 se muestra mediante: (1 to: 100)
El intervalo de 1 a 100 cada 3
1, 4 y así sucesivamente,
se muestra de la misma manera con el "by" adicional.
¿Cómo se implementa esto?
La clase Interval redefine el método printOn
y envía mensajes diferentes.
Empezamos abriendo paréntesis.
Los paréntesis aquí y aquí.
Luego mostramos el valor inicial o el primer número del intervalo,
que es este "1" aquí, y este "1" aquí.
Luego escribimos 'to:'.
Y escribimos el valor final: "100",
y "100".
Si hay un "by" eso es diferente del predeterminado "1",
escribimos el "a".
Y al final, cerramos los paréntesis.
Vimos que printString
usa el patrón de diseño template method para que las clases
puede implementar su propia representación textual.
Ahora veremos otro ejemplo, que es la copia de objetos.
¿Qué hace "copy"?
Te permite tomar un objeto y crea una copia de él.
Copiar objetos es complejo.
Puede haber diferentes estrategias.
Cada clase puede decidir
como debe lucir la copia de sus instancias.
Hay una solución simple usando template methods
que usa copy y postCopy.
copy es un template method y postCopy es un gancho.
En la clase Object hay un método copy.
Te dejaré leer el comentario.
En este metodo, enviamos shallowCopy
como un mensaje para self,
seguido de postCopy al resultado.
¿Qué hace shallowCopy?
Crea un nuevo objeto que comparte todas las variables de instancia
con el objeto base.
Tenemos dos objetos y sus variables de instancia son las mismas.
Si modifico la variable de instancia de un objeto, modifico el otro también.
Ese es el comportamiento predeterminado
de shallowCopy.
Según lo que hace postCopy,
las variables serán compartidas o no.
Si postCopy está vacío, todas las variables se comparten.
Pero las clases pueden decidir poner cosas diferentes en postCopy
para compartir ciertas variables o nada en absoluto.
La configuración predeterminada de postCopy comparte todo.
Simplemente devuelve el objeto actual.
Pero también podríamos imaginar otras aplicaciones para postCopy.
Por ejemplo, en la clase Bag.
Bag es un tipo de colección
y su método postCopy copia su contenido.
Por lo tanto, un Bag tiene una variable de instancia 'contents',
y los diseñadores de la clase decidieron
que cuando copias un Bag,
no quieres compartir la variable 'contents',
quieres variables separadas.
Tienen el mismo valor inicial pero puedes modificar solo uno.
Aquí la idea es:
postCopy es un mensaje enviado al nuevo objeto que es la copia,
que comparte todas las variables de instancia.
Si no queremos compartirlas,
creamos nuevas y decidimos sobre su valor.
Aquí en mi variable 'contents',
pondré una copia del contenido inicial.
De esa manera no los comparto.
Cada copia de mi Bag tiene sus propios contenidos.
Con el diccionario, vamos aún más lejos.
Un diccionario es una colección de pares clave-valor,
o una colección de asociaciones.
No solo queremos copiar la colección...
Si copiamos un diccionario, queremos copiar la colección,
y cada diccionario tiene su propia colección de pares ...
Pero también queremos que cada par sea diferente.
Entonces, si modifico uno, no modificará el otro lado.
Para hacer esto, copio la tabla, pero también copio
cada par adentro.
Esto es lo que está sucediendo aquí.
Para concluir, el patrón de diseño template method es muy común.
Es una señal de buen diseño.
Si haces un buen diseño de objetos, tendrás muchos template methods.
Es perfectamente normal y algo bueno.
Entonces, no dudes en usarlos.
Esta técnica permite a las subclases definir comportamiento
y modificar parcialmente el comportamiento de la superclase.