lunes, 28 de agosto de 2006

La ley de Demetrio

He aquí un claro ejemplo de lo gracioso que puede llegar a ser traducir frases en inglés. En este caso, todo un señor paradigma de la programación orientada a objetos (The Law of Demeter) nos queda reducido a algo que parece el título de una película setentera de Pajares y Esteso.

La susodicha ley especifica un método para construir clases lo más desacopladas e independientes posible, de forma que la reutilización de componentes y el mantenimiento de aplicaciones resulten tareas algo más sencillas. Concretamente, la ley dice que un método de una clase sólo debería invocar métodos pertenecientes a:
  1. El propio objeto.
  2. Los objetos creados por el método.
  3. Los objetos pasados al método como parámetros.
  4. Los campos y propiedades del propio objeto.
Si seguimos esta ley a rajatabla, esto implica, por ejemplo, que no podemos invocar métodos de objetos que nos son devueltos al invocar otros métodos. Esto puede llegar a ser complejo de cumplir.

Otra consecuencia más "razonable" es la ausencia de variables globales. La información de contexto apropiada debería estar contenida en el propio objeto como una propiedad. Aunque esto también parece complejo de cumplir, ahora veremos un ejemplo de cómo hacerlo de forma sencilla.

Tomemos por ejemplo el recurso global por excelencia de cualquier aplicación (por lo menos del tipo de aplicación que yo desarrollo): la conexión a la base de datos. Supongamos que necesitamos un objeto SqlConnection omnipresente, accesible desde cualquier lugar de la aplicación. Lo típico en estos casos es crear una clase estática de estado y almacenar la conexión como una propiedad de la misma. Entonces, las clases que necesitan acceso a la base de datos no tienen más que leer la susodicha propiedad de la susodicha clase.

Pues bien, según Demetrio, esto está MAL.

En primer lugar, la conexión no debería ser accesible a cualquier clase de la aplicación, sino sólo a aquellas de la capa de acceso a datos. Pero eso es otro tema, y tampoco quiero ponerme excesivamente repelente.

En segundo lugar, toda clase que necesite acceso la base de datos debe tener una propiedad que albergue la conexión, de esta guisa:

De esta forma, si en el futuro queremos reutilizar la clase en otro proyecto, podremos hacerlo sin problemas, puesto que no habrá ninguna dependencia de datos globales.

Y tu dirás, "¡Pero yo creo instancias de esa clase en mil sitios en mi código! ¿Tengo que establecer la conexión cada vez? ¡Qué pesadilla!" Y razón no te falta. Pero existe una solución muy sencilla: crear una propiedad estática que contendrá el valor predeterminado para la propiedad, es decir, el valor que tendrán las instancias nuevas si no se especifica lo contrario. Verbigracia:

Ahora sólo tienes que establecer el valor de la propiedad estática una vez, en el método Main que da inicio a la aplicación:

...y si para una instancia en particular quieres establecer otra conexión, puedes hacerlo tranquilamente mediante su propiedad Connection.

Y como hoy estás un poco gruñón, volverás a quejarte: "Vale, pero cada vez que creo una clase nueva con acceso a datos, tengo que acordarme de inicializarle la conexión predeterminada en el Main. Y yo tengo muchas clases y bla bla..." Tranquiiilo, que esto también tiene una solución facilona. Es hora de que la reflexión venga al rescate.

No, no se trata de que nos volvamos filósofos de repente. La reflexión es el mecanismo de .NET que nos permite obtener información sobre los componentes de cualquier ensamblado: podemos listar las clases existentes, los miembros de una clase, invocar métodos especificando su nombre, etc.

Usando reflexión, podemos decirle a nuestra aplicación lo siguiente: "Búscame todas las clases del ensamblado actual que tengan una propiedad llamada DefaultConnection, y les estableces la propiedad con este valor". Dicho y hecho:

Una solución más limpia sería crear un interfaz (por ejemplo IConnectable) que tuviera como propiedad la conexión y su alter-ego estático, y hacer que las clases con derecho a acceso a la base de datos implementaran ese interfaz. Pero eso te lo dejo como ejercicio (que además ya estoy cansado de escribir).

Conclusión: haz caso a Demetrio, que es más viejo que tú y sabe mucho.

Más información aquí.

2 puesyocreoques:

Mario Gaitán dijo...

No es necesario utilizar reflexión y todas esas cosas. Debería ser una clase que haga todas las operaciones a la base de datos y la conexión se utiliza solo dentro de esta. Una forma de utilizarla fuera de la clase sería por ejemplo enviando un objeto que implemente una interfaz algo así: DatabaseOperation{
public void execute(Connection connection);
} para que la clase ejecute ese objeto y le envíe la conexión que está utilizando.

Mario Gaitán dijo...
Este comentario ha sido eliminado por el autor.