sábado, 25 de agosto de 2007

Creación, validación e inmutabilidad de objetos

"Un objeto es una representación en nuestro modelo computacional de un ente de la realidad."
Esta definición, es según mi entender, una de las más importantes del paradigma. La cantidad de conclusiones y prácticas que se pueden derivar de ellas son múltiples, pero hoy me voy a concentrar en una que nos ha dado muy buen resultado, y tiene que ver con el momento en que se crea un objeto, el momento en que el objeto empieza a existir.
Es muy común ver modelos en donde se crean objetos y a los que se les va "pasando" sus colaboradores internos (variables de instancia) en distintos mensajes. Por ejemplo, para el caso de un fecha, es muy común ver cosas así:

unaFecha := Date new.
unaFecha year: 2007.

unaFecha monthNumber: 2.

unaFecha dayNumber: 1.

Cuando los objetos tienen protocolo de este estilo, generalmente aparecen los problemas que comento a continuación:
1) Se puede crear el objeto "unaFecha" pero nunca indicarles cuales son su año, mes y/o día. O sea, puede existir un objeto que debería representar una fecha... ¡¡pero no lo hace!!, y no lo hace porque no existe una fecha que no esté compuesta por su año, mes y día. ¿Qué problemas prácticos puede ocasionar?. Varios, pero el principal es que antes de que un objeto colabore con "unaFecha" deberá asegurarse que realmente representa una fecha, que es un objeto "completo". Todos los objetos que colaboren con "unaFecha" para saber el año por ejemplo, deberán asegurarse que el mismo no sea "nil". (¿No les parece raro? Desde el punto de vista conceptual, ¿puede un año ser "nil"?... humm, vayan pensándolo)
2) Es muy dificil que "unaFecha" se asegure que realmente representa una fecha correcta. ¿Cuándo lo hace? ¿Cuándo le indican el día? ¿y si le indican el día antes que el año o mes?. Algo es seguro, "unaFecha" no debería permitir representar algo que no es una fecha válida. Por ejemplo, los días 29, 30 y 31 son inválidos para "unaFecha" puesto que su mes es Febrero del 2007. ¿Qué soluciones se utilizan para este problema?. Una solución que se ve muy seguido es crear un mensaje llamado "validate" por ejemplo, que todas las fechas sepan responder indicando si son válidas o no. De esta manera, el objeto que está colaborando con "unaFecha" una vez que le indica todos sus colaboradores le puede enviar el mensaje "validate". ¿Qué problemas puede traer esta solución?. Un problema muy común es por ejemplo que los objetos se olviden de enviar el mensaje "validate". Y digo muy común porque ¿alguna vez ustedes le preguntaron a una fecha si es válida?. Si una fecha existe es porque es válida!!, sino no existiría, no sería una fecha (deberían empezar a ver hacia donde voy...). O sea, yo puedo hablar del 30 de Febrero de 2007, pero por más que tenga "forma" de fecha no lo es y cualquier ser humano me lo haría notar. Otro problema más "computacional" se produce si "unaFecha" es compartido desde distintos threads de ejecución, ¿cómo hacer que mientras uno lo está modificando el otro no vea que es inválido?
3) Estos objetos tienen protocolo que hacen que las fechas sean mutables, o sea que un objeto que en un instante esta representado al "01/01/2007", en otro puede representar al "30/07/2008", nada impide en este modelo que eso suceda. En el punto anterior comenté uno de los problemas que puede tener que los objetos sean mutables y tiene que ver con la concurrencia, pero más importante que este problema que reside en el plano computacional es entenderlo en el plano conceptual. Y lo importante es ver que una fecha NUNCA CAMBIA. El 25 de Diciembre del 2007 es siempre el 25 de Diciembre del 2007 no importa que le sume 5 días, le reste 4 o haga lo que haga. Una fecha no cambia de la misma manera que un número no cambia. El 2 es siempre el 2, no importa lo que hagamos con él. Si un objeto representa el ente 25 de Diciembre del 2007, una vez que lo hace no puede pasar en ningún momento a representar otra cosa. ¿Qué problema práctico traería que esto suceda?. Bueno, básicamente si ese objeto es referenciado desde varios otros objetos, solo aquel que lo modifica sabría que ahora representa otra fecha, el resto no se enteraría.
En conclusión, de estos tres puntos podemos inferir que:
1) Cuando se crea un objeto el mismo debe estar "completo" de entrada, debe representar el ente de la realidad desde el momento de su existencia, de no hacerlo habrá un tiempo (que puede ser indefinido) en el cual no representará nada y por definición no será un "buen" objeto o objeto "at all".
2) Los objetos deben asegurarse lo antes posible que son "válidos", que realmente están representando el ente de la realidad para lo cual fueron especificados. No deberían existir objetos que no representan entes de la realidad.
3) Una vez que un objeto representa un ente de la realidad no puede pasar a representar otro. ¡Solo puede cambiar si el ente que representa cambia! y aunque parezca mentira es mucho más común que los entes de la realidad no cambien a que lo hagan. Utilizando términos computacionales, cuantos más objetos inmutables existan, mejor.
Si repasamos la definición del principio, podemos ver que todas estas conclusiones se pueden inferir directamente. Si un objeto representa un ente de la realidad, apenas el objeto exista debe hacerlo, solo debe representar entes que existan y no puede cambiar si el ente de la realidad no cambia. ¡Así de simple!, ¡pero no se imaginan las derivaciones que tiene! Los resultados de seguir estas "reglas" son muy provechosas, se los puedo asegurar no solo por las conclusiones que sacamos conceptualmente, sino por la experiencia de haberlas seguido y utilizado.
Por lo tanto, el ejemplo que mostré al principio de como crear una instancia de una fecha, debería ser:
unaFecha := Date yearNumber: 2007 monthNumber: 2 dayNumber:1.
¿Simple no?. El método que implementa el mensaje #yearNumber:monthNumber:dayNumber: debería "asertar" que dicha combinación forma una fecha válida y por último, no debería existir menajes como #yearNumber: que permitan modificar una fecha.
Si pueden, traten de usar estos consejos en los modelos que hacen, les aseguro que no se van a arrepentir. Cualquier duda, avisenme!, con gusto los ayudaré.
Les dejo mientras tanto, dos preguntas:
1) Bajo estas condiciones de modelado, ¿tiene sentido utilizar "nil" para algo?, ¿deberían objetos tener variables de instancia con "nil"?
2) ¿Cuándo deben los objetos verificar si son válidos? ¿Qué deben hacer en caso de no serlo?

¡Qué tengan un día objetoso! jaja.

2 comentarios:

César dijo...

Hernán primero que nada me gustaria decir que tu blog no tiene desperdicio alguno, recién lo encontré y le vengo leyendo todo y por eso te comento en este después de casi 4 años de su creacion.
Hay una duda que me aqueja sobre lo que comentaste de crear objetos completos desde su inicio.. y es con las famosas GUI, uno esta por ejemplo acostumbrado a dar de alta una fecha en quizás, algún combo y le va eligiendo el dia , el mes, el año y asi desde este punto de vista parecería inevitable tener que manadre al objeto Date sus colaboradores a posteriori de la creacion del mismo.
Espero se entienda la duda, saludos..

Hernan Wilkinson dijo...

Que tal César, espero que veas la respuesta :-)
Justamente lo interesante de este concepto se ve cuando trabajas con UI. Para el ejemplo que das, sólo podés crear la fecha cuando tenés los otros 3 objetos (día, mes, año), por lo tanto una vez que los tenés intentás crear la fecha y si los datos ingresados no son válidos la fecha no se puede crear... Si te ponés a pensar el "modelo" que crear es una UI se puede pensar como la creación de un "árbol" de objetos donde lo que querés obtener el la raíz y llegás a ella creando los nodos de abajo hacia arriba.
Cualquier duda avisá!
Hernan.