DNM+ Online
dotnetmania 2.0
Carga dinámica de clases
En otros artículos hemos podido leer interesantes enfoques de ejecución dinámica de código. Pero a veces, esa aproximación puede suponer auténticos quebraderos de cabeza. La solución muchas veces pasa por mecanismos más sencillos, pero que parten del mismo principio: la abstracción.

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.

blog comments powered by Disqus
autor
referencias