Este patrón es de gran ayuda a la hora de separar la manipulación de datos y acceso a la DB de la lógica propia de la aplicación.
Una versión simplificada de este patrón posee tres componentes principales:
- El acceso a los datos
- El objeto de transferencia de datos (value object)
- El cliente que consume esos datos
Antes de presentar el diagrama de clases analicemos el problema el cual consiste en encontrar una forma de manipular los datos de manera tal que el modo de acceder a los mismos sea transparente al cliente que los consume. Esto quiere decir que el cliente no tiene la necesidad de conocer cómo se acceden a los datos (SQL, conectores, motor de base de datos, tipo de base de datos, etc.).
El objeto de acceso a los datos debe ser capaz de poder proveer lo que el cliente requiera y además el cliente debe poder utilizar cualquier otro objeto de acceso sin que esto afecte su lógica interna asegurando así que el sistema funcionará aún cuando se cambie de motor de base de datos, de conexión, e inclusive si se modifican las consultas SQL.
En resumen, en una situación trivial tendríamos que el cliente, supongamos una pantalla que lista todos los usuarios del sistema (por cuestiones prácticas asumiremos que al decir pantalla incluimos todos los componentes necesarios para que la misma sea funcional, es decir: vistas, controlladores, modelos, etc.), le solicita al objeto de acceso a datos que le provea todos los usuarios activos del sistema. Este a su vez le solicita a la fuente de datos (puede ser un archivo, una base de datos, o inclusive otro objeto) los datos que cumplen con el criterio establecido por el cliente.
El objeto de acceso a datos (a partir de ahora lo llamaremos DAO) retorna a la pantalla (Cliente) una colección de resultados que cumplieron con el criterio. Donde cada resultado es representado por un objeto de transferencia (Value Object ó Transfer Object. Resumidamente VO) que no es más que una clase donde cada atributo se corresponde con una columna de nuestra tabla (en el más simple de los casos). Esta colección de VOs es retornada al Cliente mediante el DAO y el Cliente manipula cada VO de acuerdo a sus necesidades ya sea de procesamiento (transformación de los datos en información para el usuario) o de presentación (simplemente visualización de los mismos).
Yendo más en profundidad, en el DAO podemos encontrar métodos que cuyo único propósito sea ejecutar una consulta a la base de datos, o una búsqueda dentro de un archivo. Dentro de este método pasará una de dos alternativas, si se obtuvieron resultados se deberá generar por cada uno un VO y añadirlo a una colección. Si no hubieron resultados se puede generar una colección vacía o retornar un valor nulo. Es recomendable generar una colección vacía para que luego el cliente pueda esperar siempre una colección que podrá tener cero o más elementos.
Siguiendo con nuestro ejemplo, tendríamos un DAO que llamaríamos UsersDAO que provee el método findActive(). De esta manera el Cliente podría tener una instancia de UsersDAO que utilizaría para invocar este método que a su vez devolvería una colección de VOs (la colección será un array, un contenedor del tipo Map o List. Dependiendo del lenguaje que se utilice esto puede variar)
Grafiquemos lo que acabamos de ver y luego veamos un ejemplo de código fuente.
Tengan en cuenta que este patrón puede ser implementado en cualquier lenguaje, pero para hacer más sencilla la explicación veremos el código en PHP.
class UsersDAO{ public function findActive(){ $users = array(); $sql = "SELECT * FROM users WHERE status = 'Active'"; // Asumimos que la conexión existe y que el objeto admite el método execute() // execute () devuelve false en caso de error y una colección objetos estándar por cada registro $result = $this->connection->execute( $sql ); if( $result ){ foreach( $result as $row ){ $users[] = $this->recordsetToUserObject( $row ); } return $users; } } public recordsetToUserObject( $row ){ $user = new UserVO(); $user->setUsername( $row->username ); $user->setFullname( $row->fullname ); return $user; } ... } |
class UserVO{ private $username; private $fullname; // setters public setUsername( $username ){ $this->username = $username; } public setFullname( $fullname ){ $this->fullname = $fullname; } // getters ... } |
class Client{ public function listActiveUsers(){ $usersDAO = new UsersDAO(); $users = $usersDAO->findActive(); // Visualización de los usuarios ... ... } } |
Tips!
- Aplicar siempre un patrón. Cada problema que se presenta puede ser resuelto aplicando uno o más patrones, e inclusive adaptando los ya existentes. Esto ahorra mucho esfuerzo a la hora de implementar una solución y asegura un buen nivel de calidad en el producto final.
- Una tabla un DAO. Por cada tabla o fuente de datos se debería crear un DAO y un VO. Dependiendo de la relación que existan entre cada tabla se podrá crear un solo DAO para varias tablas.
- Esto es una guía. Pueden combinar este patrón con otros para cada parte del sistema que estén desarrollando. Habrá veces que este patrón no es muy óptimo y deberán realizar ajustes o inclusive utilizar otro patrón.
- Mantener la coherencia. Es fundamental más allá de los patrones que utilicen, mantener ciertos criterios de diseño para que aún implementando diferentes soluciones para el mismo problema el sistema tenga una coherencia razonable, esto ayudará a que todos los desarrolladores puedan seguir el código sin dificultades.
- No abusar de los casos excepcionales. Como en todo hay casos excepcionales que deberán ser resueltos de forma excepcional y hasta rompiendo la coherencia que pretendemos seguir. Sin embargo, se puede subsanar este quiebre en la coherencia simplemente agregando comentarios al código que brinden las explicaciones pertinentes de por qué se hizo de esa manera y no de otra. Esto evitará que el próximo que venga se encuentre con algo que no tiene sentido aparente y lo modifique para después darse cuenta que no era necesario cambiarlo. Pero contemplar casos excepcionales debe ser un último recurso siempre hay que indagar lo suficiente para descartar la posibilidad de incorporar dicho caso al resto y así evitar una lógica particular únicamente para ese caso.
- Reducir los métodos con demasiados parámetros. Al escribir las consultas SQL a veces se tiende a hacer una única consulta dinámica y según los parámetros pasados se va construyendo el filtro. Esto traten de evitarlo ya que si se produce un error en la consulta durante el uso normal se les hará dificil detectar de donde viene el problema ya que por todos el sistema se llama al mismo método con distintos parámetros. Tener dos queries aparentemente idénticas pero que varía un único parámentros puede ser la diferencia entre un sistema robusto y un sistema inestable.
- Usar clases especializadas. Tener pocas clases que hagan todo o tener muchas clases que hagan poco siempre fue, es y será una decisión que el desarrollador deberá tomar en algún momento de su vida. Si bien todo depende del tipo de proyecto en mi opinión es mejor tener muchas clases que hagan poco ya que esto facilitará el mantenimiento del sistema a lo largo del tiempo. No es lo mismo cargar una clase de 3 mil líneas para usar 10 métodos que cargar 5 clases de 100 líneas cada una que contienen únicamente esos 10 métodos.
- Comentar el código no es traducir el código. Cuando escriban comentarios en el código eviten comentar los IFs de la siguiente manera "Pregunta si $username es 'pepito', si no es entonces guardo el nombre en $currentUsername sino ..." Estos tipos de comentarios no sirven para nada. Un buen comentario explica en lenguaje natural lo que el código hace y no lo que el código dice. Tiene que estar dirigido a otro desarrollador diferente al creador de ese código, al propio creador y a cualquier persona que deba validar el cumplimiento de los requerimientos y que no necesariamente sabe leer código fuente.
Hola interesante tu post, pero no me queda claro.
ResponderEliminarPor ejemplo si tengo una tabla ARTICULOS, tengo que crear un ARTICULOSDAO donde estarán todos sus métodos.
Y ARTICULOVO que me permitirá visualizarlo. O tengo que crear varios VO's según mi necesidad?
Gracias.
Diana, muchas gracias por tu comentario.
ResponderEliminarLa implementación de patrones siempre es a conveniencia y puede ser modificado según ésta.
Dicho esto, en un sentido estricto del patrón, por cada tabla tendrías una clase DAO y una clase VO. Pensalo de la siguiente manera, la clase DAO es la que te brindará la facilidad de tener un único lugar dónde agrupar las consultas a la base de datos (dicho en la forma más simple), mientras que la clase VO es la representación de un registro de la base de datos a la cual le podés agregar comportamiento.
En principio no sería necesario crear varias clases VO. A menos que necesites una distinción por tipo de artículo, lo cual se traduce en una clase VO por cada tipo de articulo, pero nuevamente no es requerido por el patrón sino que dependerá de tu necesidad o simplemente por comodidad.
Espero haya aclarado tu duda y sino, por favor no dudes en escribir nuevamente proporcionando más detalles del problema para poder ayudarte.
Saludos y gracias
Podrías recomendarme alguna bibliografía del tema?
EliminarEstoy tratando de entender este manejo, entiendo lo del DAO pero no me queda muy claro lo del VO.
Gracias.
Un buen libro sobre patrones es "UML y Patrones" de Craig Larman. Quizás este link más detallado te pueda a ayudar a comprender mejor el patrón
Eliminarhttp://www.oracle.com/technetwork/java/dataaccessobject-138824.html
Tengo una consulta con respecto al uso de los Value Objects con PHP5:
ResponderEliminar1) Existe algún formato específico o recomendación para crear el VO de una tabla?
2) Si dices que hay un DAO y un VO por cada tabla de la base de datos mi consulta es si mi tabla tiene 6 campos el array que captura la información del select es este:
while($fila=$recordSet->FetchRow()) {
$array[] = new TablaVO($fila['cam1'], $fila['cam2'], $fila['cam3'], $fila['cam4'], $fila['cam5'], $fila['cam6']);
}
Pero qué pasa si solo quiero trabajar con dos campos? Cómo trunco a los demás campos para que no me genere el error?
Hola David,
Eliminar1) No existe un formato específico, sin embargo la recomendación básica es que la clase VO tenga al menos un atributo por cada campo. Puedes encontrarte con que un campo en la tabla es una Foreign Key de otra por lo que ahi deberás decidir si en tu clase VO lo vas a tratar como un campo simple o si lo vas a tratar como un objeto que será otro VO correspondiente a la tabla relacionada. Este último escenario es mucho más complejo y quizás la solución en estos casos sea utilizar un ORM ( Object-Relational Mapping ).
2) Para lograr lo que dices, sólo debes pensarlo de forma más general y versátil. Imagina que algunas tablas tienen 20 campos, por lo que no es muy óptimo ni buena práctica pasar 20 argumentos a un método. Asi es que yo te recomiendo es que utilices los métodos "accessors" de la clase VO, de esa manera puedes setear los valores de los atributos que desees, esto obliga a que en tu VO definas valores por defecto en el constructor para todos los atributos.
Otro tip que te doy es que el VO no es un array, es un objeto.
Entonces, modificando un poco tu código te quedaría algo así:
$listObjects = array(); // Colección de objetos VO
while($fila=$recordSet->FetchRow()) {
$registroVO = new TablaVO();
$registroVO->setFieldOne( $fila['cam1'] );
$registroVO->setFieldTwo( $fila['cam2'] );
$registroVO->setFieldThree( $fila['cam3'] );
// Si quieres más campos usas el setter correspondiente
$listObjects[] = $registroVO; // Agregas el objeto a la colección
}
Saludos