Ese concepto que nos ayuda a buscar la definición de la solución a
un problema, en vez de abordar técnicas de fuerza bruta
(implementación) para resolverlo. En nuestros días se hace muy
necesario el plantearse una buena arquitectura de software
que:
a) Garantice un software muy modular.
b) Adaptable a los cambios del negocio en tiempos
récord y con el mínimo impacto.
c) Que pueda ser extensible a nuevas
especificaciones sin tirar todo el trabajo hecho por la
borda.
Y esto muchas veces no ocurre en los desarrollos actuales. Por
ejemplo, quién -en ASP.NET o Visual Basic .NET-, no coloca en el
mismo manejador del evento, el código que ejecuta una acción de
negocio, en vez de sacar ese código a una clase, y llamar a la
clase. O quién no coloca en una opción de menú (definida en diseño,
claro) el código que dispara la llamada a un diálogo que realiza un
proceso. Todo es muy típico. Y muchos pensaréis que es lo suyo.
Pero creo que si os dieran tiempo para pensarlo veríais que repetís
ese mismo principio para cada proyecto. Esto nos tiene que dar
lugar a pensar que muchos procesos del software en el que reside la
lógica de negocio, NO dependen de dicha lógica de negocio. Si no
que proporcionan un valor añadido. Por ello, muchas veces es
increíble oír que tenemos que recompilar todo un proyecto sólo por
el hecho de que un informe cambie de filosofía, o mejor aún
tengamos que incorporar nuevos informes. En este caso, el problema
del software no está en la implementación de CADA informe, sino de
intentar entender que para el software el problema está en que debe
presentar UN informe en un momento dado -el que determine el
usuario en su lógica de negocio-, y que este concepto de informe
será elegido por el usuario en función de sus necesidades; y que
estas necesidades cambian. Pero para la arquitectura del software,
será mostrar un informe. El cómo se implemente dicho informe será
una decisión que se tome en función de qué tipos de informes estén
establecidos, cuál es el que le haga falta al usuario, etc. Es
decir, que nuestra misión es aprender a separar la abstracción de
la implementación. Que cualquier tecnología orientada a objetos
-.NET en este caso- nos permite implementar empleando los conceptos
de Interfaz <-> Clase empleando como pegamento un cargador
dinámico de clases.
El análisis
Qué necesitamos. Una aplicación que permita mostrar informes de una
base de datos. Por ahora nuestro usuario quiere un informe de
empleados en una aplicación para Windows. Pero más adelante quiere
uno de productos, y más adelante, pues como que no se acuerda…,
pero sabe que querrá más. El segundo problema, que cada informe
necesita de un aspecto diferente. El de empleados lo quiere con una
rejilla de datos al estilo Access y el de productos, con un control
de lista de esos que tienen iconos. Por último, no quiere tener
problemas con los informes, esto es, que no quiere que se le meta
mano a su máquina con instalaciones ni historias. Que le gustaría
algo del estilo a Windows Update.
Bien, pues con estos requisitos, empezaremos a darle vueltas al
coco. De acuerdo que el que conozca ADO.NET puede ver cómo queremos
las cosas (todos de acuerdo en que una buena lógica de negocio
depende de XML como estructura de almacenaje de datos, dejando la
implementación en una capa de encapsulación dentro de un objeto).
Pero tenemos un problema: cada informe tiene un aspecto
potencialmente diferente. Nuestra aplicación debe mostrar un menú
con los informes disponibles… pero claro:
a) ¿Qué informes han sido ya implementados?
b) ¿Va a haber más informes?
c) ¡Necesitaremos muchas recompilaciones!
d) Los informes seguramente cambien mucho de
aspecto…¡tendremos que refactorizar de nuevo, y recompilar, y
redistribuir!...una locura.
Solución. Un poco de UML aclarará la historia. El problema radica
en que nuestra aplicación sabe que tiene que mostrar informes pero
ni cuáles ni cómo. Nuestros informes saben lo que tienen que hacer
pero no cuando. Por tanto:
a) Nuestra aplicación requiere tener claro qué
entiende por informe.
b) Cada informe tiene que ser una implementación
específica de lo que la aplicación entiende por informe. Habrá
tantos componentes como distintos tipos de informes tengamos ahora,
o en un futuro.
c) Que la aplicación debe cargar los componentes
en tiempo de ejecución (momento en el cual el usuario sabe lo que
quiere), y para ello debe asegurarse de que las DLL que debe
cargar, cumplen con la especificación de que son informes. Y puesto
que la carga se hace en tiempo de ejecución es un momento ideal
para aplicar en el acto nuevos cambios de versión, nuevos informes,
etc.
Si -como decía- aplicamos un poco de UML, esto puede quedar tal y
como vemos en la figura 1.
Este análisis quiere decir:
a) InterfazInforme representa el concepto del
informe que tiene nuestra aplicación. Es un tipo de objeto que debe
tener la operación de Mostrar().
b) Que los objetos InformeEmpleados e
InformeProductos son dos clases que implementan la especificación
del interfaz. Es decir, deben codificar el método Mostrar().
c) Cargador es una clase responsable de la carga
dinámica de los informes de tipo InterfazInforme.
d) GestorInformes es el contenedor responsable de
mostrar los informes. Nuestra aplicación principal. Por lo tanto,
estará compuesto de una agregación de n-diálogos.
Un refinamiento. Puesto que es una aplicación Windows, requeriremos
que sea una aplicación de tipo MDI. Por ello, especificaremos como
parámetro de todo informe, el que podamos elegir quién es el padre
del informe. Por esto, el interfaz obligará a pasar como parámetro
a mostrar, una referencia a una ventana que actuará como padre del
documento MDI Hijo.
El explorador de soluciones de nuestro proyecto. Tal y cómo aparece
en al figura 2 es cómo queda nuestro proyecto. Hubiera estado bien
acabar el UML, pero no hay espacio suficiente en disco… je, je,
je.
La implementación
Los componentes de negocio
Ahora viene la parte divertida: codificar el sistema. Empezaremos
por el interfaz y las clases de informe. Estarán almacenadas cada
una de ellas, en un proyecto de tipo "librería de clases", quedando
como en el fuente 1.
Ahora vienen los componentes con los diálogos de los informes. Y al
loro que aquí hay truco. Crearemos un proyecto del tipo "Biblioteca
de clases", pero quitaremos la clase que genera la plantilla y
añadiremos al proyecto un objeto de tipo Windows Form. Esto genera
el código de un formulario (¡dentro de la DLL!). Y como todos
pensamos ya, será este formulario el que extenderá nuestro interfaz
de tipo Informe. Quedando el código como el del fuente 2.
Siendo el aspecto gráfico de esta DLL algo del estilo a lo que
vemos en la figura 3.
De manera similar, creamos el componente del segundo informe. Sólo
cambia el aspecto del diálogo y su código SQL. Ver fuente 3.
El aspecto de nuestra DLL CoInformeProductos queda tal y como lo
podemos ver en la figura 4.
El programa principal
Para terminar con nuestra aplicación, sólo nos queda implementar el
programa principal. Éste tiene como misión preparar de forma
dinámica un menú de opciones que debe mostrarnos los diferentes
tipos de informes de los que dispondrá el usuario.
Para ello, necesitaremos especificar de alguna manera la relación
de informes disponibles, y qué mejor forma de hacerlo que empleando
un poco de XML. Dispondremos pues de un fichero de inicio XML que
documentará qué opciones tenemos y qué componentes deben ejecutarse
como fruto de la selección de dicha opción. Para ello dejaremos el
formulario principal tal y cómo se muestra en la figura 5.
Antes de comenzar es importante que marquéis una referencia al
proyecto del interfaz: CoInterfazInforme. Y sólo a él; los demás
componentes no son necesarios. El sistema los cargará en tiempo de
ejecución.
Todo el follón comienza en el evento Load del formulario. Aquí
cargaremos la lista de opciones y prepararemos las opciones
dinámicas. Para ello emplearemos el pedazo objeto DataSet y
convertiremos el XML en un contenedor de opciones: Titulo,
Componente y Tipo. En la implementación ¿qué técnicas se van a
usar?: Crear dinámicamente un menú y asociarle un delegado en
tiempo de ejecución que responda al evento OnClick. Quedando el
código de OnLoad cómo puedes ver en el fuente 4.
Donde el manejador genérico que hemos creado para cada opción de
menú, y que es responsable de la carga dinámica de clases lo puedes
ver en el fuente 5.
La explicación a este código es: Creamos un objeto DataView para
encontrar la fila que coincide con la opción de menú seleccionada.
A continuación viene el proceso de la carga dinámica de clases,
para la cual hay que tener en cuenta:
a) .NET carga nuevos tipos de datos a partir de
librerías de componentes.
b) Una vez cargado el tipo, éste se mantiene en
el espacio de nombres de la aplicación todo su ciclo de vida.
c) Por lo tanto, la carga de la librería sólo hay
que hacerla una vez. El resto del tiempo, nos limitaremos a pedirle
tipos.
Activator es esa clase de .NET que nos permite poder jugar con el
milagro de la carga "en caliente" de nuevas librerías de objetos.
Obviamente, cuando cargamos el tipo por primera vez, debemos
aislarlo de la identificación de la librería como tal, de ahí que
pasemos antes por ObjectHandle. Al final, lo único que buscamos es
el unboxing del tipo que ya conocemos pero que no sabemos cómo
funciona: el tipo InterfazInforme, que nos permitirá activar el
diálogo oportuno de acuerdo a nuestras reglas: un interfaz MDI que
tiene un padre, que es la ventana principal.
El despliegue
El despliegue es sencillo. Debemos colocar todo "parcialmente"
junto. ¿Por qué digo esto? pues porque las DLLs pueden estar donde
nos dé la gana; desde el mismo directorio del ejecutable como en un
directorio virtual bajo el IIS. .NET Framework está más que
capacitado para eso.
En nuestro ejemplo, y por simplificar las rutas, lo pondremos todo
junto. Pero, mejor, en un subdirectorio del EXE porque en un futuro
pueden llegar más DLLs y no queremos mezclar las cosas. ¡Abajo el
infierno de las DLLs!
Eso sí, en el mismo directorio del EXE necesitaremos un sencillo
XML que documente las opciones del menú y apunte a los componentes
de los informes. Ver fuente 6.
La Ejecución
Pues nada, sólo me queda mostraros la captura del sistema
funcionando y enseñándoos lo que se puede hacer con un poco de
paciencia y un buen análisis. El código fuente os lo dejo en la web
de dotNetManía y lo tenéis todo organizado como sólo el Visual
Studio lo puede hacer ...
La conclusión
Ya hemos visto cómo funciona la carga dinámica de clases. Pero me
quedan dos coletillas. En primer indicar lo que alguno estará
pensando… ha puesto la conexión dentro de los eventos de los
diálogos en lugar de abrirla y compartirla. Pero en mi defensa diré
que .NET proporciona pool de conexiones, por lo tanto no tengo que
preocuparme de ello, y es más, cumplo con todas las reglas de la
orientación a objetos: usa las cosas cuando las necesites, y
estrictamente cuando las necesites y libéralas cuando no las
necesites.
En segundo lugar, que nuestro programa principal es tan flexible
que ya no debemos tocarlo. Sólo extenderlo ampliando el modelo de
componentes y configurando apropiadamente el XML. De acuerdo que
hay pequeños flequillos que se podrían optimizar..., pero para eso
ya estáis vosotros. Por mi parte no quería complicarlo más con
adornos. Espero que estéis de acuerdo.
Para terminar no puedo evitar pensar que alguien puede comentar:
¡jope!, ¿por qué no se libera la DLL en lugar de estar todo el
tiempo cargada? ¡Qué desperdicio! Ante esto, sólo comentaros que
imagino que es una decisión de los ingenieros de Microsoft. Una vez
cargado el tipo, éste queda cargado en el espacio de nombres y no
se puede desligar la DLL. Alternativas a esta arquitectura: los
dominios de aplicaciones, Remoting, Serviced Components y/o
Servicios Web. ¡Quién da más!... Pero lo dejaremos para otros
artículos.