Este video se enfocará en un aspecto avanzado de Pharo,
que son las operaciones reflexivas necesarias para la programación en vivo que usamos.
Analizaremos lo que sucede cuando recompilamos una clase.
Y explicaremos las operaciones reflexivas
necesarias para implementar programación en vivo.
Este es un caso típico.
Por lo general, comenzamos por definir una clase,
luego agregamos métodos y creamos instancias de esa clase.
Luego redefinimos la clase y agregamos nuevas variables de instancia.
Entonces, ¿qué hacemos con las instancias que ya existían
cuando hay una variable de instancia menos?
Debemos configurar un mecanismo para migrar estas instancias
a la nueva versión de la clase.
Luego seguimos trabajando.
Después de eso, veremos las operaciones
que habilitan la redefinición dinámica de clases, recompilación de métodos,
y que permiten
una migración de instancias automática y transparente
a la nueva versión de la clase.
Para hacer esto, necesitamos recopilar todas las instancias de clase.
Entonces tenemos que cambiar todo lo que usaba estos objetos
y asignar sus punteros a nuevos objetos.
¿Vamos?
Primero, ¿cómo recopilamos todas las instancias de una clase?
Si enviamos el mensaje allInstances a una clase,
muestra todos los objetos que son sus instancias.
Es una coleccion de todos los objetos estándar.
Si lo enviamos a la clase de Window, obtenemos una colección de objetos de ventana.
Podemos recuperar el primer elemento y envíarle un mensaje "close"
ya que el método "close" se define en la clase Window.
Otra primitiva reflexiva es "pointersTo".
Si enviamos este mensaje a un objeto, accedemos a la colección
de todos los objetos que almacenan una referencia al mismo.
Esto es muy útil
cuando queremos intercambiar objetos.
La primitiva para intercambiar punteros es "become".
La idea es habilitarnos para intercambiar dos objetos.
Queremos intercambiarlos simétricamente.
Todo lo que apuntaba a este objeto en el sistema,
en este caso tenemos dos punteros,
Se invertirá para que los elementos ahora apunta a este objeto.
Rompemos los enlaces para los punteros que estaban de este lado,
por lo que cambian de objeto.
"Become" significa invertir todos los punteros en el sistema simétricamente.
Aquí hay un ejemplo.
Yo creo un pt1,
que apunta al objeto "0 @ 0" y una variable pt2,
que también apunta al objeto "0 @ 0".
Además de una variable pt3 que apunta al punto "100 @ 100".
Entonces escribo la primitiva "pt1 become: pt3".
Todo lo que apuntaba al objeto denotado por la variable pt1
ahora apuntará al punto 3.
Es decir, todos los punteros a pt1.
Vemos que la variable pt2 apunta al mismo objeto que pt1,
y ahora apunta a lo que fue referenciado por pt3.
En cuanto a pt3, ya que es simétrico, apuntará a lo que apuntó pt1.
Hasta arriba
Y pt1 apunta a lo que apuntó pt3.
Los punteros han sido intercambiados simétricamente.
La variante asimétrica es "becomeForward".
Esto indica que queremos intercambiar punteros.
Todo lo que apuntaba a este objeto se pasará a ese objeto.
Pero no al contrario. Los punteros a este objeto permanecen iguales.
Aquí hay otro ejemplo usando puntos.
Cuando ejecutamos un "becomeForward",
vemos que impacta en pt1 y pt2.
pt3 no se vio afectado por el "becomeForward",
y los que apuntaban a este objeto no se modifican.
Otra primitiva reflexiva es "adoptInstance".
Esto cambia la clase de un objeto.
Le pedimos a una clase que adopte una nueva instancia, pasada como un parámetro.
Cambiar una clase es herramienta muy poderosa, pero algo limitada.
Es esencial que la clase original del objeto
en este caso, "anInstance",
sea compatible con el formato de la clase objetivo.
No puedes simplemente intercambiar cualquier objeto.
A veces está indexado, etcétera.
El formato de un objeto es muy importante.
Miremos que es una clase en esencia.
Una clase es esencialmente un formato.
Un formato especifica el número de variables de instancia
y el tipo de variables, como explicamos en un curso anterior.
Tiene una superclase y un diccionario de métodos.
Aquí tenemos "Behavior class", que es una superclase
de "Class class", que define el comportamiento básico de las clases.
Es el mínimo básico para una clase.
Entonces, una clase tiene una superclase, un diccionario de métodos,
y especificaciones de formato.
Ahora resumiré todas las características de comportamiento reflexivo
que hemos discutido hasta ahora,
que dan ciertas instancias comportamiento que es específico de ellas.
Explicaré el código a medida que avancemos.
Vamos a crear una instancia de la "Behavior class" en estas tres líneas.
Recuerda, es una clase.
Creo una instancia de la clase, que yo llamo Behavior.
Eso me da un objeto llamado Behavior.
Indico que este objeto hereda de la clase Model.
Como esto.
Configuro el formato de este objeto Behavior.
Entonces creo una instancia de la clase Model.
Es un objeto llamado Model.
Aquí está la línea importante en este código.
Cambiaré la clase de este objeto Model
para convertirse en una clase del objeto pasado como un parámetro, llamado Behavior.
Rompo el enlace y lo hago una instancia de esta clase.
Esta es una subclase de la clase Model.
Ahora puedo compilar Un nuevo método en esta clase.
Compilo el método #foo, que devuelve 2
Estoy haciendo esto en el objeto Behavior.
Como podemos ver aquí,
si envío el mensaje foo al objeto Model,
como aquí,
regresará 2.
Esto sigue el método de búsqueda.
Envío el mensaje foo.
El objeto Model busca el método en su clase, el objeto Behavior,
y encuentra con éxito el método.
Funciona a la perfección.
Pero si creo una nueva instancia de clase Model,
como esta,
y le envío el mensaje foo,
se indica un error via MessageNotUnderstood.
Porque si aplico ToLookUp,
miro en la clase del objeto, Model class,
pero no encuentro el método foo en su diccionario o superclases.
Este mecanismo avanzado nos permite dotar a una instancia específica
de la clase Model con un comportamiento.
Aplicemos esto a la clase Set para aclararlo.
Creo set1, una instancia de la clase Set.
El diccionario de método de clase Set contiene el método add:.
Para una instancia específica de la clase Set, Quiero cambiar el método add:,
que tiene un comportamiento específico para un conjunto específico.
Entonces, creo una clase "anónima" aquí.
Es una clase específica que hereda. de la clase Set.
Aquí está su herencia.
El objeto set2 es una instancia de esta clase "anónima".
En su diccionario de métodos, Redefiniré el método add:
dándole un comportamiento particular.
Ahora, si envío el mensaje add: al objeto set2,
este método será seleccionado,
y tendrá un comportamient específico, diferente
del método add: aquí si hubiéramos enviado el mensaje a este conjunto.
Entonces, tenemos dos conjuntos que reaccionan De manera diferente al mensaje add:.
Aquí está el código para llevar a cabo este ejercicio.
Al igual que antes, creo una clase
como una instancia de "Behavior class".
Establezco la superclase como "Set class".
Configuro el formato.
Compilo el método add: en esta clase anónima.
Estoy redefiniendo el método add: que está define en la clase Set.
Uso "Transcript show" para mostrar la ejecución del método add:.
Entonces llamo "super" para ejecutar el método add: en la clase Set.
Ahora probaremos este código. creando un primer set.
Aquí tengo la clase Set.
Creo la primera instancia de esta clase, llamada Set.
Le envío el mensaje add:.
Envío add:1, que usa el método agregar:de la clase Set.
Si pido la clase Set aquí, Voy a tener Set.
Pero ahora ejecutaré este primitiva que ves aquí.
Le pido al objeto Set que cambie las clases.
para usar la clase Behavior de antes, que es una subclase
de la clase Set.
Entonces, cambia y se vuelve una instancia de la clase Set,
que tiene una nueva versión del método add:.
Ahora, si envío el mensaje add: 2 a este objeto,
seleccionará el método add: desde el objeto Behavior,
y ejecutará este código.
Podemos verificar en el Transcript
si el método se ha ejecutado.
Eventualmente se ejecutará,
ya que también usamos "super" para el método add: de la clase Set.
Esto nos permite espiar en instancias seleccionadas de Set,
usando esta línea aquí.
Para concluir,
todas estas operaciones reflexivas nos permiten crear herramientas muy innovadoras.
Podemos probar e implementar nuevas funciones
gracias a estas primitivas reflexivas, que promueven la extensibilidad del lenguaje.
Pero hay una regla de oro cuando se usa la reflexión.
No hay que usarlo de manera inapropiada. en el código de dominio.
Limitamos el uso de códigos reflexivo en el código de dominio.
La reflexión puede violar el encapsulamiento de los objetos.
Realmente puede ...
Puede sobrepasar reglas de programación de objetos,
y por lo tanto está reservado para la construcción de herramientas.