viernes, 16 de enero de 2009

Tip 2: Clearly identify what represents the identity of an object

En el primer Tip (si ya se, lo escribí hace mucho...) comenté sobre lo interesante que es tener como objetivo lograr un isomorfismo entre los objetos de mi modelo y los entes del dominio del problema.
Además, como ya puse en otros posts, no hay que perder de vista que los objetos representarán la esencia de dichos entes mediante su comportamiento, no estructura, relaciones, etc.
Sin embargo hay otro punto del que siempre hablamos y es la identidad. La identidad es la que permite identificar únivocamente un ente. Todo ente posee una identidad y por lo tanto el objeto que lo representa en nuestro modelo también debe tener una. Sin embargo hay una gran confusión entre lo que es identidad, lo que algunos llaman "estado de un objeto" y lo que es la identidad del objeto. Voy a empezar por este último.
Un objeto, por ser a la vez un ente de la realidad (además de estar representado otro) también posee una identidad. Esa identidad en un ambiente de objetos está generalmente implementada por medio de la posición de memoria que ocupa el objeto. Es lo que hace que ese objeto sea único, no puede haber otro objeto en la misma zona de memoria que otro. 
En Smalltalk, se puede saber si dos objetos ocupan la misma zona de memoria usando el mensaje #== Este mensaje tiene por propósito verificar si dos "objetos" son identicos, o sea, si ocupan la misma zona de memoria pero no puede decir nada sobre la identidad del ente que representan dicho objetos. En rigor de verdad, si dos objetos son idénticos significa que están representando al mismo ente y por lo tanto representan la misma identidad del ente, pero si dos objetos no son identicos no se puede asegurar que estén representando a entes no idénticos. Es acá donde se ve claramente la diferencia entre identidad de objeto e identidad de ente. Un ejemplo lo va a aclarar.
En Smalltalk existe la clase Date, pero si evalúo dos veces la siguiente colaboración "Date today" obtengo dos objetos que no son idénticos pero que sí representan al mismo ente, el día de hoy. Por lo tanto no hay que confundir identidad de objeto que viene dado por el ambiente de objetos de identidad del ente que la tenemos que definir nosotros en nuestro modelo.
¿Cómo representamos la identidad del ente? Dependerá del ente que estemos modelando o la implementación que utilicemos. Por ejemplo, los SmallInteger en Smalltlk nos dan la sensación de ser únicos (o sea, de que hay uno y solo un objeto por cada uno de ellos). A nivel implementativo sabemos que no es así, pero es un claro ejemplo donde la identidad del ente esta asociada a la identidad del objeto. Otro ejemplo son los objetos instancia de Symbol, no puede haber más de un objeto para el mismo símbolo que representan de la realidad. Como vismo, no sucede así con las fechas y con otros objetos como aquellos instancias de Time, Point, etc.
¿Qué es lo que representa entonces la identidad de los entes para estos objetos? Siempre es un conjunto de colaboradores internos (variables de instancia) que cumplen ese propósito. En el caso de Date, dependiendo de la implementación serán el número de día, mes y año. En el caso de Point serán esos objetos que representan la coordenada x e y. 
Una característica interesante que tenemos que cumplir con estos colaboradores que representan la identidad de los objetos es que no pueden cambiar, puesto que si lo hacen el mismo objeto estaría en un instante de tiempo representando un ente y luego otro, sería un objeto con "problema de personalidad" no les parece?. Es por ello que es fundamental entender bien qué colaboradores internos representan la identidad de un objeto puesto que ellos no pueden cambiar. Hay otras implicancias interesantes de este hecho, la primera es que el mensaje #= del objeto devolverá true si los colaboradores internos que representan la identidad del ente son iguales. Esto significa que si queremos saber si dos objetos representan el mismo ente, debemos usar el mensaje #=, es este mensaje el que nos dirá si dos objetos representan a un ente idéntico o en otras palabras, a exactamente el mismo ente. Moraleja, no confundir identidad de objeto de identidad de ente y por lo tanto nunca, pero nunca usar el mensaje #== en un modelo a menos que ese modelo esté tratando con un dominio de problema computacional y donde realmente se tenga por intención saber si se está tratando a exactamente "el mismo objeto" sin importar si además si representan exactamente el mismo ente.
Esto que estoy comentando les debería disparar una nueva explicación de porque el hash debe estar relacionado con el #= y por qué cuando un objeto cambia los colaboradores que representan la identidad cambia su hash que termina produciendo un descalabro importante en aquellos objetos que dependen de la invariabilidad de esta característica.
También esto tiene que empezar a dispararnos la idea de que hay colaboradores internos que representan la identidad del ente que modela el objeto y otros que no. ¿qué características pueden tener estos otros? ¿pueden cambiar? ¿que representan?. Son muchas preguntas, no tengo una respuesta 100% segura, pero en la mayoría de los casos me parece que dichos colaboradores existirán por motivos implementativos como por ejemplo tener un cache, poder acceder más rápido a un objeto, etc. y no a cuestiones esenciales del dominio de problema en si, pero no lo puedo asegurar.
Otra pregunta interesante para hacerse es, ¿debería el lenguaje ofrecerme herramienta particulares para hacer uso de esta diferencia? Por ejemplo, que defina una clase de esta manera:
Object subclass: #Point
   identityDefinedBy: 'x y'
   instanceVariableNames: ''
   ... etc.
Al hacerlo, automáticamente el ambiente podría asegurar que la identidad no puede cambiar, y algo más interesante aún que la misma se defina en el momento de crear el objeto, algo que veremos en el próximo tip.... mientras tanto tiro la idea, yo aún no tengo un posición tomada.

3 comentarios:

Gaboto dijo...

Hola Hernan, muy buen post. Veo que volviste a escribir cosas interesantes :). Unos comentarios.
1) Cuando decis "¿Qué es lo que representa entonces la identidad de los entes para estos objetos? Siempre es un conjunto de colaboradores internos (variables de instancia) que cumplen ese propósito.", Entiendo lo que querés decir, pragmáticamente sirve y está bien, pero no me parece del todo correcto.
Me parece que lo que identifica a un objeto, con respecto al ente que representa, es como responde al enviarle cualquier mensaje (Excluyendo obviamente el mensaje ==, o cualquiera que no tenga que ver con el dominio y tenga más que ver con lo implementativo). Es decir, dos objetos representan el mismo ente si se comportan igual, independientemente de cómo estén implementados.
Por ejemplo, un conjunto de números podría estar representado con una instancia de HashSet y otra instancia de RBTreeSet, serían dos objetos que representan el mismo ente y esto no se determina por comparación de algún subconjunto de colaboradores internos.
Igualmente, como dije antes, pragmáticamente sirve lo que dijiste en la mayoría de los casos en que hablamos de dos instancias de la MISMA CLASE.
2) También decis: “También esto tiene que empezar a dispararnos la idea de que hay colaboradores internos que representan la identidad del ente que modela el objeto y otros que no. ¿qué características pueden tener estos otros? ¿pueden cambiar? ¿que representan?. Son muchas preguntas, no tengo una respuesta 100% segura, pero en la mayoría de los casos me parece que dichos colaboradores existirán por motivos implementativos….”.
Ahora, te pregunto: no todos los colaboradores que cambian son implementativos, también podrían ser esenciales de la entidad no? O sea, en la realidad hay entes que mutan algunas de sus características. El tema es que si existen varios objetos que representan la misma entidad, todos deben mutar esas mismas características en el mismo instante. Estas de acuerdo con esto?

Por otro lado, me pareció excelente la idea de poder separar ambos tipos de variables de instancia.
Saludos!

Hernan Wilkinson dijo...

Que tal Gaby, gracias por tu comentario. Acá van mis respuestas:
1) Sobre este punto, me parece que estás confundiendo la cosa. Dos objetos pueden responder exactamente igual a distintos mensajes ya así y todo representar entes distintos. Un ejemplo medio loco, dos ruedas que están recién hechas en teoría responderían igual a los mensajes que les envíes pero son dos entes distintos que deberían estar representados por dos objetos distintos. Respecto del ejemplo que das, si hay dos objetos que "simultáneamente" están representando al mismo ente, estás en problemas, no se debería dar esa situación. Si no es así entonces lo que comentás no debería pasar.
2) Concuerdo con lo que decís de que no todos los colaboradores son implementativos, no me expresé correctamente. Pero lo que sí sucede es que si los colaboradores forman parte de lo que define la identidad de un objeto, no pueden cambiar, lo que puede cambiar es aquello que no forma parte de su identidad. Más adelante volvés a poner que "hay vario objetos que representan la misma entidad"... yo no estoy de acuerdo con eso, no debería pasar, no te parece?

Gaboto dijo...

En realidad tenés razón. Sucede que me confunde el hecho de que a los fines prácticos, cuando programamos sistemas puede suceder que por una cuestión implementativa, tengamos dos objetos representando al mismo ente de la realidad. Es cierto que no es correcto, a veces esto trae problemas obvios como el problema de sincronización entre ambos.
En fin, es bueno saber que en estos casos, si tomamos estas decisiones, tendremos que lidiar con un problema de identidad de los objetos.
Por otro lado, estaría bueno tener mas en claro qué representa el mensaje = (igual). Porque muchas veces no tenemos conciencia de que este no habla sobre la identidad de los objetos en particular.