viernes, 31 de agosto de 2007

¿Cuándo verificar si un objeto es válido?

Otra de las preguntas que quedó pendiente del post donde recomiendo crear objetos válidos desde el principio es cuándo conviene verificar si el objeto es válido.
Lo primero que debemos determinar es de quién es la responsabilidad de verificar si es válido el objeto. Se cae de maduro que los que no pueden realizar esa verificación son los objetos que lo usan, por lo tanto quedan dos opciones: 1) La clase de la cual será instancia el nuevo objeto 2) El objeto mismo a ser validado.
Veamos un ejemplo práctico. Un precio es un ente de la realidad que dice cuanto cuesta algo[1]. Por ejemplo el precio de 1 Kilo de Bananas es 10$. Para representar "precios" podemos crear una clase, Precio, la cual sabrá responder al mensaje #para:es:. Por lo tanto, una colaboración para crear precios sería:

Precio para: 1*Kilo*Banana es: 10*Peso

(Este ejemplo supone que están usando Aconcagua o algún paquete de medidas similar y por lo tanto puede crear medidas multiplicando cantidades por unidades, por eso 10*Peso es equivalente a lo que comúnmente denominamos "diez pesos". Por supuesto este ejemplo también asume que Kilo, Banana y Peso son objetos accesible en el contexto de evaluación y representan unidades).
Lo que tiene de particular los precios es que los mismos existen solo para cantidades distintas de 0. O sea, no tiene sentido hablar de el precio de 0 kilos de banana o de 0 autos. El motivo por el cual no tiene sentido se puede ver si representamos el precio como una cuenta matemática (en definitiva lo es). Un precio es la división del valor por la medida a la cual se le asigna el precio. Por lo tanto, si el precio para un kilo de bananas es 10 pesos, matemáticamente (y en Smalltalk) se representa como:

(10*Peso) / (1*Kilo*Banana)

Como pueden ver, no tiene sentido que el divisor de esta división sea 0. Es esta condición por lo tanto, la que se debe verificar cada vez que se crea un precio.
Veamos como se implementaría la opción 2). Esta implica que primero se crea el objeto y luego se valida. A nivel código la implementación sería:

Precio class>>para: unaMedida es: unValor

^self new initializePara: unaMedida es: unValor

Precio>>initializePara: unaMedida es: unValor

unaMedida isZero ifTrue: [ self error: 'No se puede crear un precio con cantidades en 0' ].
medida := unaMedida.
valor := unValor.

La implementación de la opción 1) sería:

Precio class>>para: unaMedida es: unValor


unaMedida isZero ifTrue: [ self error: 'No se puede crear un precio con cantidades en 0' ].
^self new initializePara: unaMedida es: unValor

Precio>>initializePara: unaMedida es: unValor

medida := unaMedida.
valor := unValor.

Dadas estas dos implementaciones, ¿cuál les parece más correcta?.
¿Lo pensaron?.... tomensé unos minutos... no lean la solución que propongo directamente!
[... pensando ... ]
Bien, asumo que ya lo pensaron. La opción correcta es la 1. ¿Por qué? Simplemente porque en la opción 2) el objeto ya fue creado, el precio ya existe pero es inválido! por lo tanto esta rompiendo la premisa que comenté en el post anterior, que solo pueden existir objetos válidos!. Es este el motivo principal por el cual la opción correcta el la 1. En este caso el objeto solo se crea si la validación es correcta.
Otra ventaja que tiene la opción 1 es que se crearan menos objetos en el caso de tener muchos casos inválidos... dependiendo del comportamiento de la aplicación esto puede llegar a impactar en performance puesto que la creación de objetos está directamente relacionada con la del garbage collector.

Bueno, espero que les haya gustado este tipo de heurísticas de las que vine hablando. Estaría bueno que me comenten que les parece... siempre es bueno un poco de feedback para repensar la cosas y seguir escribiendo.

[1] Un precio en realidad es algo más complejo que únicamente el valor de una cantidad de algo, los precios varían para la misma cantidad de ese algo según el momento en que se toma (el precio de hoy puede ser distinto al de ayer, o el de ahora puede ser distinto al de una hora antes) y el lugar donde se generan (el precio en Carrefour de las bananas no es el mismo que el de Jumbo)

4 comentarios:

Anónimo dijo...

Hola Hernán, estoy completamente de acuerdo con no crear objetos inválidos pero tuve un problema a la hora de implementarlo en una situación particular.
Te doy el ejemplo concreto:
tengo un objeto que representa un cliente. un cliente para ser válido tiene que tener una condicion(frente al iva), nombre o razon social.
Usando MVC tengo una vista de un cliente.
El cliente lo crea el usuario a través de la vista.
La vista debe observar un cliente que ya se encuentre creado, y esto antes de que el usuario haya puesto los parámetros para que sea válido.
¿Se entiende el problema? es un poco difícil de explicar. Es un problema de que el objeto cliente debe ser creado antes que el usuario ingrese los parámetros, porque es necesario que sea observado por la vista.
Bueno, muchas gracias!!!
Saludos

Diego Campodónico.

Hernan Wilkinson dijo...

Que tal Diego,
muy interesante tu pregunta. MVC es un framework que nos ha dado mucho para aprender y que tiene cosas muy interesantes, pero es su versión original, te obliga a tener objetos inválidos por el problema que vos comentas.
Nosotros hemos encontrado la forma de resolver este problema por medio de la composición de objetos del modelo y lo que llamamos "controllers" (que no son los controllers de mvc) que se encargan de crear dichos objetos e ir componiéndolos. Es un framework intersante y que aún no hemos podido publicar nada al respecto por falta de tiempo... si querés un día te lo comento personalmente.

Saludos,
Hernán

Anónimo dijo...

Hola Hernán, gracias por la respuesta, espero que algún otro día me lo comentes porque me interesa.
Tengo otra duda con respecto a este tema.
¿Qué pasa si tengo dos objetos con una relación bidireccional entre ellos y para que sean válidos uno necesita del otro?
Te lo ilustro con un ejemplo:
Tengo un objeto un equipo y un objeto que representa jugadorDeEquipo.
Un jugadorDeEquipo para que sea válido debe conocer(o tener) su equipo ( o sea que el equipo debe existir antes de la creación del jugadorDeEquipo) y un equipo paraque sea válido debe conocer (o tener) a sus jugadores ( o sea que los jugadorDeEquipo debe existir antes de la creacion del equipo).
con esto llegue a una contradicción. (:S)
Gracias y saludos!!!

Diego Campodónico.

Hernan Wilkinson dijo...

En ese caso tenes que hacer que uno sea mutable (por un momento nomás) o ver si dicha relación no debería estar en otro objeto... a veces pasa que lo mejor es lo segundo.

Saludos,
Hernan.