Google Analytics

martes, 21 de febrero de 2012

Objetos valor

Vaya, he estado ojeando los borradores acumulados en mi cuenta de Blogger y parece que por aquí había una idea que no estaba mal. No se porque razón no completé este artículo y lo publique, pero como nunca es tarde si la dicha es buena, allá vamos.

Hace poco (bueno, ahora ya hace más de año y medio :-) leí un artículo de Krzysztof Adamczyk sobre la potencia de hacer uso de objetos valor en nuestro código, y he pensado que podría ser interesante que presente mi propia opinión al respecto de este tema. Para ello tomaré prestadas las ideas del propio Krzysztof Adamczyk y las expuestas por Dan Berg Johnsson en su presentación en la QCon London 2009.

*Nota al lector*

El siguiente código no es de producción, tan solo es a modo de ejemplo. Está realizado en Scala, pero cualquier persona con conocimientos de Java lo puede entender de forma sencilla.

¿Qué es un objeto valor?

Lo mejor será empezar por el principio. Para todos aquellos que no sepan lo que es un objeto valor, Eric Evans define en su libro un objeto valor de la siguiente manera.
"An object that represents a descriptive aspect of the domain with no conceptual identity is called a Value Object."
He preferido dejarlo en el idioma original porque es posible que yo metiese la pata al hacer la traducción, por lo que con esta definición y lo que cada uno entienda de ella, empezaré con el contenido de esta entrada.

Los detalles del dominio se hacen explícitos

Una de las ventajas de hacer uso de los objetos valor, es que en el código desaparecen los tipos como String o int por tipos como PhoneNumber o Email. Aquí muchos pensaréis que esto no es ninguna ventaja sino que añade complejidad, puede ser pero dejadme unos párrafos de ventaja por favor.

Imaginad que estamos trabajando en un nuevo y avanzadísimo sistema de gestión ... como por ejemplo, una simple una agenda. Como en cualquier agenda, tendremos un objeto de modelo que define el contacto y generalmente nos encontraríamos con algo similar al siguiente código.

class Contact(val firstName: String, val lastName: String,
 val phoneNumber: String) {
 
 override def toString(): String = {
  firstName + " " + lastName + " : " + phoneNumber
 }
}

object Agenda {
 
 def main(args: Array[String]) = {
  val contact = new Contact("Pepe", "García", "922222222")
  println(contact)
 }
 
}

Genial, muy sencillo, pero ahora imaginemos  que nuestro cliente quiere que su agenda indique de forma automática con un icono (que nosotros representaremos en el ejemplo con texto) cuando se trata de un teléfono fijo y cuando de un móvil, por poner un ejemplo. Muchas veces la opción más utilizada es la siguiente.

class Contact(val firstName: String, val lastName: String,
 val phoneNumber: String) {
 
 def phoneNumberType(): String = {
  if (phoneNumber.startsWith("6"))
   "móvil"
  else
   "fijo"
 }
 
 override def toString(): String = {
  firstName + " " + lastName + " : " + phoneNumber + 
   " (" + phoneNumberType + ")"
 }
}

object Agenda {
 
 def main(args: Array[String]) = {
  val pepe = new Contact("Pepe", "García", "922222222")
  println(pepe)
  val juan = new Contact("Juan", "Juanez", "633445566")
  println(juan)

 }
 
}

Pero ¿qué ocurriría si hiciésemos esto otro?.

class PhoneNumber(val phoneNumber: String) {

 def withType(): String = {
  phoneNumber + " (" + phoneType + ")"
 }
 
 private def phoneType(): String = {
  if (phoneNumber.startsWith("6"))
   "móvil"
  else
   "fijo"
 }
 
}

class Contact(val firstName: String, val lastName: String,
 val phoneNumber: PhoneNumber) {
 
 override def toString(): String = {
  firstName + " " + lastName + " : " + phoneNumber.withType
 }
 
}

object Agenda {
 
 def main(args: Array[String]) = {
  val pepePhone = new PhoneNumber("922222222")
  val pepe = new Contact("Pepe", "García", pepePhone)
  println(pepe)
  val juanPhone = new PhoneNumber("633445566")
  val juan = new Contact("Juan", "Juanez", juanPhone)
  println(juan)

 }
 
}

Ciertamente son bastantes más líneas de código, pero ¿qué hay de la limpieza? y aún más importante, de esta manera se cumple la regla que dice que cada elemento debe tener una única responsabilidad. Ahora la clase Contact no tiene lógica sobre la forma en la que se muestra el número de teléfono.

Al tratarse de un ejemplo hemos metido lógica de la vista en el modelo, pero en un caso real lo que haríamos es tener un método en PhoneNumber que nos indique de que tipo de dispositivo se trata, y ya se encargará la capa de la vista de representarlo.

El código se extiende más fácilmente

Sigue sin parecerte nada especial y la legibilidad del código no te importa mucho, aunque debería. Pues otra ventaja es que el código se extiende más fácilmente y de manera comprensible. Imagina que ahora queremos saber qué números de teléfono tienen errores, pues basta con que añadamos dicha funcionalidad en el propio objeto PhoneNumber, sin seguir complicando otras clases ni desperdigar la lógica por el resto de la aplicación.

Para acabar me gustaría señalar como de esta manera hemos evitado tener un modelo anémico. Es muy usual que se creen modelos como si fueran simples CRUD y luego se reparta cierta lógica en validadores y helpers, ¿no es más lógico que sea el propio objeto el que tenga la lógica que le afecta únicamente a él?



No hay comentarios: