|
Patrones en .NET
Código fuente usado en el artículo
Cuando construimos una pieza de software, desde un pequeño
utilitario, hasta un sistema completo distribuído, estamos
encarando un trabajo de análisis, diseño y desarrollo. Esa
actividad puede realizarse en grupo o individualmente. En
muchas situaciones, hemos notado que la implementación de
una solución, en un caso de diseño o de programación, es
similar a otra que hemos adoptado en el pasado. También
suele suceder que descubrimos, más adelante, que la solución
adoptada para un problema en particular, sea opacada por
otra solución no contemplada inicialmente. Cuantas veces nos
"despertamos" a una nueva alternativa de implementación,
al ver el código o el esquema de la solución de otro
producto. Es común encontrar también pistas e ideas interesantes
en los artículos y libros que vamos consultando.
Siendo la creación de software, una actividad humana que
se viene desarrollando desde hace décadas, no es extraño que
hayan surgido esquemas de soluciones a problemas planteados
anteriormente. Y, ante tanta "crisis del sofware", (el siempre
presente problema de generar soluciones en tiempo y costo),
esta situación ha incentivado la aparición de metodologías de análisis, de
diseño, de implementación, y de manejo de proyectos.
En el agitado mundo de estas metodologías (reflejo de
los perpetuos cambios a los que nos tiene acostumbrada
nuestra profesión), a fines del siglo
pasado, surge el concepto de "Patterns" (patrones). He preferido dejar el título en inglés, pues
es al que nos hemos acostumbrado en estas latitudes. Veamos
en este artículo, qué son estos patterns, y cómo se pueden
aplicar en .NET, la nueva tecnología de Microsoft, que tanta
sorpresas nos depara (desde la ansiada herencia, hasta
la máquina virtual base de todo, pasando por el marco de clases
comunes).
Un pequeño ejemplo
El patrón resulta ser una solución
a un problema, que se puede aplicar muchas veces, en distintas
situaciones. Veamos un caso en particular.
Supongamos que tenemos una clase SistemaDeFacturacion, que
concentra la actividad de generar facturas. Queremos que el
resto del sistema, interactúe con una sola instancia de esta
clase, porque es importante para nosotros no generar recursos
duplicados, y controlar el acceso a los mismos. ¿Cómo podemos
conseguir este resultado?
Podemos escribir el código:
public class SistemaDeFacturacion
{
//NOTA: Sólo para aplicaciones de un thread
private static SistemaDeFacturacion _Instancia = null;
private SistemaDeFacturacion() {}
public static SistemaDeFacturacion GetInstancia()
{
if (_Instancia==null)
{
_Instancia = new SistemaDeFacturacion ();
}
return _Instancia;
}
//... otras funciones de Sistema de Facturacion
}
Siempre he pensado, que para entender realmente una parte del conocimiento
humano, es importante conocer la historia de su desarrollo. Esto
nos permite entender las motivaciones para su aparición, así
como las dificultades, elecciones, y falsos caminos tomados. Y hasta
quizás, participar de alguna medida, en su posterior desarrollo.
De aquí, la necesidad de los siguientes párrafos.
Algo de historia
Curiosamente, el concepto de patrón de diseño, no nace
en el software, sino en otra actividad, en la arquitectura (la
de construcción de edificios). Como la nuestra, esta actividad
se dedica a construir, y no es extraño que comparta vocabulario
y conceptos.
Christopher Alexander es el arquitecto que primero estudió
el concepto de pattern, en el contexto de construcción de
edificios y comunidades. El escribió, ya en 1977:
"Cada pattern describe un problema que ocurre una y otra vez
en nuestro entorno, y además, describe el núcleo de la solución
a ese problema, de tal manera, que podemos usar esa solución
un millón de veces más en el tiempo, sin que tenga que ser
la misma cada vez"
El escribía acerca de patterns en la arquitectura, pero lo que
describe, se puede aplicar a la ingeniería del software. Expresaremos
nuestra solución en términos de objetos e interfaces, en lugar
de usar paredes, espacios y edificios. Pero al fin, usaremos
mismos principios para expresar un pattern.
A fines de los ochenta, el término "software architecture" era
ya usado normalmente, y en las reuniones de especialistas,
iba naciendo la idea de un manual para arquitectos de sistemas,
una especie de guía o hasta enciclopedia, de las prácticas
habituales en la construcción de sistemas.
Ya había habido algún antecendente ilustre, como el conocido
"The Art of Computer Programming" del notable Donald Knuth. Su
intento de catalogar el conocimiento acumulado sobre la programación,
estuvo centrado en los algoritmos. Aún hoy es la referencia
sobre algunos de esos temas. Algunas otras publicaciones
habían tratado otros aspectos. El libro seminal de Coplien
"Advanced C++: Programming Styles and Idioms" (tengo en mi
poder un ejemplar, realmente disfruté mucho leyéndolo), mostró
ciertas soluciones específicas a C++, por ejemplo, una que
permitía construir una clase String (tan necesaria en
un lenguaje que derivaba de C, donde el concepto de string
como tipo primitivo no existía), de una forma eficiente.
Por otra rama, en la de desarrollo en Smalltalk (más que un
lenguaje o una tecnología, un ambiente de objetos), se comienzan
a describir patrones, soluciones que ya se habían aplicado en
problemas anteriores, notablemente en la propia librería base
del lenguaje (como el caso del notify en Object, preludio de
los actuales Observer). El mítico Kent Beck tuvo la idea de
difundir el trabajo de Alexander, tan alejado al principio
del software, entre la comunidad smalltalker, desde su columna
en "The Smalltalk Report". Peter Coad también trabaja en esos
tiempos en coleccionar patrones. En un escrito de 1992, "Object
Oriented Patterns", en las comunicaciones de la asociación
ACM (Association for Computer Machinery, http://www.acm.org),
comienza a presentar el uso de patrones en las etapas de
análisis y diseño.
Pero es con el trabajo de Gamma, Helm, Johnson, Vlissides,
"Design Patterns", en 1995, subtitulado "Elementos de software orientado
a objetos reusable", cuando el tema se pone maduro. Estos
autores, conocidos afectuosamente como "GoF" (la "Gang of Four",
la banda de los cuatro), son los que toman el trabajo algo
monumental, de hacer un catálogo de los patrones de diseño que
hasta ese momento habían aparecido, y en una lista de 23
patterns, tratan de enumerar el conocimiento acumulado
sobre el tema. Este libro (también en mi biblioteca, uno
de esos libros a salvar de un incendio, junto con el Rey Pastor,
y alguno como los "Elementos de Historia de las Matemáticas"
de Bourbaki), se ha convertido en un escrito de culto para
los estudiosos, y realmente se lo merece, por el orden y la
claridad y extensión de su exposición.
¿Qué es un Design Pattern?
Pero basta ya de introducción histórica, pasemos a describir
qué es un patrón de diseño (que no es la única clase de patrón
existente, pero es el más conocido).
Más arriba, hemos encontrado una cita de Alexander, donde menciona
que un pattern, es "la solución a un problema". Según "GoF" un
patrón tendrá entonces, cuatro elementos esenciales:
- El nombre: Este elemento, que alguien podría pensar que
es intrascendente, o trivial, ha probado que ser fundamental.
A cada patrón popular, se le ha asignado una denominación que
permite que los entendidos en el tema, puedan conversar usando
un diccionario común. Nos permite un mayor grado de abstracción.
Nos permite comunicarnos con nuestros colegas, construir un
lenguaje compartido, y hasta nos ayuda a nosotros mismos, al
ordenar un patrón bajo un nombre. Según "GoF", uno de los
problemas que tuvieron, fue encontrar un nombre apropiado
para cada patrón que catalogaron.
- El problema: acá se explica el problema original, y su contexto.
Puede describir desde detalles específicos, como algoritmos, o
clases y estructuras que se han encontrado inflexibles a la
hora de implementarse. Pero primero, todo patrón nace de un
problema a solucionar.
- La solución: nos encontraremos, en nuestro estudio de los
patrones, que cada uno es en realidad una solución a un problema,
el elemento planteado arriba. Hasta puede que un mismo problema
real, tenga dos soluciones parecidas, correspondientes a dos
patrones (muchas veces pasa esto con el Abstract Factory, versus
el Factory Method). Pero la elección seguramente recaerá en
el patrón que mejor se adapte al contexto particular del
problema que tengamos entre manos. Resaltemos que la solución
que un patrón describe, no necesariamente es detallada al
nivel de implementación, sino que provee una descripción
abstracta, una enumeración de elementos y sus relaciones, para
solucionar el problema planteado.
- Las consecuencias: son los resultados de aplicar el patrón,
los "trade-off", compromisos, que se tienen que aceptar al
adoptar el mismo. Como dicen los americanos, "no free lunch", no
hay almuerzos gratis, de alguna forma hay que pagar lo que
aparece especialmente barato. En general, en software, la moneda de pago
de pago es el espacio y el tiempo. A veces, un patrón nos soluciona
un tema de espacio (como el Flyweight), a costa de una mayor
complejidad en otra punta de nuestro desarrollo.
Hay algo a esta altura del artículo para destacar: vemos que
un patrón siempre tiene un problema, y una solución. Pero también
notemos, que los catálogos de patrones que han aparecido, destacan
que los elementos clasificados, siempre son patrones que ya
se han aplicado exitosamente con anterioridad. No se ha dado
el caso de un patrón creado de la nada, o simplemente para
engrosar el catálogo. Cada patrón que veamos en la literatura,
se verá respaldado por aplicaciones anteriores. El trabajo
del catalogador, ha sido descubrir la esencia del patrón,
para poder describirlo en un nivel mayor de abstracción, y
aprovechar así su poder en otros ámbitos y contextos parecidos.
Describiendo un Pattern
He aquí algunos un formato consistente, para describir un patrón,
adoptado en el libro de Gamma:
Nombre del patrón y clasificación
Cada patrón tiene un nombre, y una categoría a la que pertenece.
El "GoF" los clasifica en creacionales, estructurales y de conducta.
Más sobre esta clasificación más adelante.
Intención
Nada se da en la nada, sino en un contexto. Cada patrón tiene
una intención, una razón, una justificación, algo como "¿qué problema de
diseño trata de abordar?"
Otros nombres
O el A.K.A. ("Alsa Known As") del patrón. Ahora menos usado, pero
en su tiempo, los patrones habían surgido en distintos proyectos
y tecnologías. Los primeros autores les dieron un nombre que
no siempre coincidía. Estos otros nombres pueden ser enumerados
en la descripción del patrón.
Motivación
Más que la intención, esta parte describe un escenario: un problema
en particular que aparece, y que ayuda a entender la descripción
algo más abstracta del patrón genérico.
Aplicación
En qué situaciones puede ser aplicado un patrón. Pueden darse
ejemplos de malos diseños, que pueden beneficiarse de la aplicación
de este patrón en particular.
Estructura
La parte más conocida, luego del nombre. Se adopta un diagrama
basado originariamente en la OMT ("Object Modeling Technique"),
hoy se usa UML ("Unified Modeling Language", Lenguaje Unificado
de Modelado). Si bien no se necesita entender al detalle esta
notación, cuando aparezca, en este artículo daremos nociones
de la misma.
Partipantes
La lista de las clases y objetos que participan del patrón de
diseño, y las responsabilidades que tienen.
Colaboraciones
Descripción de cómo los participantes colaborar para llevar a
cabo sus responsabilidades en el patrón
Consecuencias
Explica cómo el patrón cumple con sus objetivos, y que compromisos
se asumen.
Implementación
Esta es la parte que más puede variar, porque para cada patrón
puede haber varias posibles implementaciones, incluso, diferentes
implementaciones según la tecnología adoptada. Veremos que hasta
puede haber sutiles diferencias entre una implementación y otra,
ligadas, por ejemplo, al lenguaje de implementación.
Código de ejemplo
Los buenos de la "GoF", siempre nos dan código de ejemplo de
cada patrón, por ejemplo, en Smalltalk o en C++. Han aparecido
luego libros con patrones aplicados a Java, y comienzan a
aparecer las implementaciones .Net.
Usos conocidos
Un patrón no es tal, si no ha sido ya empleado en algún caso
real. Se debe incluir por lo menos dos ejemplos conocidos de
difentes ámbitos.
Patrones relacionados
Ya hemos apuntado que un patrón es una solución a un problema.
Puede que haya problemas parecidos, que tengan más de una solución.
De ahí que muchos patrones estén relacionados entre sí: como el
caso del Proxy y del Adapter. Se trata de explicar acá cuáles
son las similitudes y (no menos importante) las diferencias, entre
patrones relacionados.
Clasificación de Patrones
Gamma y sus colaboradores, se concentran en los patrones de
diseño, y los clasifican en las categorías. Agreguemos un
resumen de los patrones que encontraron en cada una.
Patrones Creacionales
("Creational Patterns") Abstraen el proceso de instanciación. Nos ayudan a independizar
a un sistema, de cómo sus objetos son creados. En general,
tratan de ocultar las clases y métodos concretos de creación,
de tal forma que al variar su implementación, no se vea
afectado el resto del sistema.
Es común encontrar "competencia" entre estos patrones: hay
más de un patrón a adoptar ante una situación.
Hay varios ejemplos en .NET de aplicación de estos patrones.
Podemos nombrar acá al WebRequest.Create, que nos devuelve
un objeto de distintas clases, dependiendo de lo que le
aportemos como parámetros en ejecución.
Abstract Factory: Nos da una interface para crear objetos
de alguna familia, sin especificar la clase en concreto.
Builder: Separa la construcción de un objeto complejo, de su
representación. De esa manera, el mismo proceso de construcción
puede crear diferentes resultados.
Factory Method: Se define una interface para crear objetos,
como en el Abstract Factory, pero se delega a las subclases
implementar la creación en concreto.
Prototype: Mediante una instancia prototípica, conseguimos
otras instancias de ese objeto.
Singleton: Nos consigue dar un solo objeto de la clase, en
cualquier momento de la aplicación.
Estructurales:
("Structural Patterns") Se ocupan de cómo clases y objetos se agrupan, para formar
estructuras más grandes. Podemos nombrar al clásico patrón
Composite, que permite agrupar varios objetos como si fueran
uno solo, y tratar al objeto compuesto de una forma similar
al simple. El clásico ejemplo es el de una clase Arbol, donde
cada Nodo no importa si es un NodoCompuesto o uno simple. Encontramos
en .NET una aplicación de este patrón en el XmlDocument.
Adapter: Permite convertir una interface de una clase, en otra,
que es la esperada por algún cliente.
Bridge: Desacopla una abstracción de su implementación en
concreto. Luego, podemos cambiar la implementación, o la
abstracción, sin cambiar la otra.
Composite: Compone objetos en una estructura de árbol, donde
los objetos compuestos se tratan de forma similar a los
objetos simples.
Decorator: Agrega responsabilidad a un objeto dinámicamente,
dándonos una alternativa a la extensión de una clase, en lugar
de usar subclases.
Facade: Provee una interface unificada a un conjunto de
funciones de un subsistema. Es una interface de alto nivel,
para facilitar el uso del subsistema.
Flyweight: Permite compartir objetos, sin repetirlos en el
sistema, eficientemente.
Proxy: Provee un subrogado a otro objeto, para controlar el
acceso al mismo.
De Conducta:
("Behavioral Patterns") Más que describir objetos o clases,
sino la comunicación entre ellos. Frecuentemente, describen
las colaboraciones entre distintos elementos, para conseguir
un objetivo. Como muestra, en .NET tenemos los Enumerator, que
implementan el patrón Iterator, una forma de recorrer una
lista o colección, sin afectar a este elemento.
Chain of Responsability: Nos desacopla el enviador de un
mensaje, de su receptor, permitiendo que haya varios objetos
que tengan la oportunidad de manejar el requerimiento. Eso
se consigue pasando el requerimiento por la cadena de objetos
hasta llegar al encargado de atenderlo.
Command: Encapsula el requerimiento a un objeto, permitiendo
incluso el "undo" de la operación.
Interpreter: Construye una representación de la gramática de
un lenguaje, junto con su intérprete.
Iterator: Nos da un modo de acceder a los elementos de un
objeto colección o similar, sin exponer su estructura interna.
Mediator: Permite la interacción de varios objetos, sin generar
acoples fuertes en esas relaciones.
Memento: Sin necesitar entrar en la estructura interna de un
objeto, permite capturar su estado, para, por ejemplo, poder
restaurarlo más adelante.
Observer: Define una relación uno a muchos, entre un objetos
y otros que están interesados en sus cambios, de nuevo, sin
caer en el acople entre los mismos.
State: Permite a un objeto cambiar su conducta cuando cambia
su estado interno, simulando que cambia de clase.
Strategy: Define una familia de algoritmos, y los hace
intercambiables.
Template Method: Define el esqueleto de una operación, cuyas
operaciones más básicas, quedan delegadas en subclases.
Visitor: Nos permite recorrer una estructura (un árbol, por
ejemplo), aplicando una operación a cada elemento.
Para cada uno de estos, el libro mencionado da un detalle exhaustivo,
incluyendo código de ejemplo, e implementaciones conocidas.
Analicemos entonces, más en detalle, algunos de estos patrones,
desde el punto de vista de .NET.
El patrón Singleton
Habíamos mostrado un ejemplo de patrón, llamado Singleton. Ahora
describámoslo de forma más acorde a lo que se habitúa en la
literatura.
Contexto
En algunas situaciones, necesitamos que algunos datos sean
accesibles desde el resto del sistema. Y también necesitamos
que esos datos sean únicos. Por ejemplo, un objeto que abstraiga
lo que es el Sistema Operativo actual. También podría ser
un objeto que represente al Sistema Contable, que sea el
punto de entrada a un sistema externo.
Problema
¿Cómo hacer que la instancia de un objeto sea accesible
globalmente, y que sea única?
Fuerzas
Consideremos las fuerzas que intervienen en las posibles soluciones.
- Hay lenguajes que soportan la existencia de variables globales,
como Visual Basic 6, o Visual C++. En estos lenguajes, esa
variable reside en la espacio de nombre raíz, y pueden ser
accedidas desde cualquier otra parte del sistema. Esto puede
solucionar el requerimiento de que sea visible globalmente, pero
no el de instancia única. Hay otros lenguajes que no tienen
el concepto de variable global.
- Si necesitamos que la instancia sea única, debemos tener
control de la creación de las instancias de la clase. Habrá
que implementar algún mecanismo para que no cualquiera pueda
crear una instancia.
Solución
El patrón Singleton proporciona la siguiente solución:
- Hacer que la clase provea una instancia de sí misma.
- Permitir que otros objetos obtengan esa instancia, mediante
la llamada a un método de la clase.
- Declarar el constructor como privado, para evitar la
creación de otros objetos.
El diagrama UML correspondiente es muy simple:
Este diagrama UML muestra que Singleton es una clase. Contiene
una propiedad estática (el subrayado indica que es un método
de clase, mas que de instancia), que retorna un Singleton, un
objeto de la misma clase. Según la notación UML el número 1
en la esquina superior derecha, indica que solamente habrá
una instancia de esta clase. El signo "-" en el constructor,
lo señala como privado. Esto consigue que nadie aparte de
la propia clase, pueda crear una instancia.
Implementación
Public Class Singleton
Private Shared mInstance As Singleton
Private mTime As String
' Constructor privado
Private Sub New()
mTime = Now.ToLongTimeString
MsgBox("Objeto Singleton Creado en New")
End Sub
Public Shared Function GetInstance() As Singleton
If mInstance Is Nothing Then
mInstance = New Singleton()
End If
Return mInstance
End Function
Public Function GetTime() As String
Return mTime
End Function
End Class
Esta es una implementación en VB.NET. Notamos que el método
GetInstance está marcado como "shared", siendo entonces un
método de la clase.
Conseguimos que el constructor no pueda usarse desde otra
parte del sistema, catalogando con "private" al Sub New.
Notemos un detalle en esta implementación, que no es parte
del patrón original: deferimos la creación del objeto hasta el
momento que alguien pide la instancia. Esto puede tener
sus razones: tal vez no tengamos toda la información sobre
cómo crear el objeto, hasta el momento apropiado.
Como toda implementación, puede tener alguna arista sutil. La
que no es evidente, en este ejemplo, es ésta: no contempla
la posibilidad de dos "threads" (hilos de ejecución) que, al
mismo tiempo, pidan el método GetInstance, por primera vez. Si
así sucediera, podría suceder que estos dos hilos de ejecución, cada
cual por su parte, encuentre en "Nothing" a la instancia
interna, y hagan cada uno un "new", creando dos instancias.
Este es un ejemplo mejorado, que resuelve ese problema,
usando un objeto Mutex:
Public Class Singleton
Private Shared mInstance As Singleton
Private Shared mMutex As New System.Threading.Mutex()
Private mTime As String
' Constructor privado
Private Sub New()
mTime = Now.ToLongTimeString
MsgBox("Objeto Singleton Creado en New")
End Sub
Public Shared Function GetInstance() As Singleton
mMutex.WaitOne()
If mInstance Is Nothing Then
mInstance = New Singleton()
End If
mMutex.ReleaseMutex()
Return mInstance
End Function
Public Function GetTime() As String
Return mTime
End Function
End Class
Veamos otra implementación, esta vez en C#.
public sealed class Singleton
{
private static volatile Singleton instance;
private static object syncRoot = new Object();
private Singleton()
{
System.Windows.Forms.MessageBox.Show("Nuevo Singleton");
}
public static Singleton GetInstance
{
get
{
if (instance == null)
{
lock(syncRoot)
{
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}
}
En algunas situaciones, nos basta implementar la creación de
la única instancia, en la inicialización de las variables
estáticas.
public sealed class Singleton2
{
private static readonly Singleton2 instance = new Singleton();
private Singleton2(){}
public static Singleton Instance
{
get
{
return instance;
}
}
}
El patrón Observer
Examinemos este patrón, y su implementación en .NET.
Contexto
Siempre que pensamos en objetos, podemos encapsular su estado
y la implementación de su comportamiento. Esto permite reusarlo
en otras aplicaciones, de forma natural. Pero un objeto no es
un ente aislado. Es común que deba colaborar con otros para
cumplir con una tarea. A veces, la colaboración es altamente
acoplada: los objetos intervinientes conocen sus interfaces de antemano.
En otras situaciones, algunos objetos solamente estarán interesados
en los cambios que se ejecuten en otro determinado, sin que éste
necesariamente tenga que conocer a los primeros. Este es el
caso del patrón Model-View-Controller, que permite separar
el modelo que estamos manejando, de la forma de mostrarlo (la
vista). La vista (un control en un formulario, un gráfico),
puede que esté interesada en conocer cuándo cambia el modelo
subyacente.
Problema
¿Cómo podemos notificar a otros objetos que el estado de un
objeto determinado cambió, sin depender de qué clases son?
Fuerzas que intervienen
Una posible solución, es que el objeto que cambia, llame
directamente a los objetos interesados. Pero esto crea una
dependencia entre ellos, que podemos evitar. Porque el
acoplamiento dependiente que se crea, dificulta el reúso
y complica los cambios futuros en las relaciones.
Un cambio en la vista, si adoptamos la llamada directa, podríaa
afectar entonces a la programación del modelo. La necesidad
de evitar esa dependencia se torna importante. Por otra parte,
si tenemos que agregar una nueva vista, tendremos que
modificar el código del modelo, que avisa de sus cambios.
Por otra parte, la llamada directa puede ser el método más
rápido de avisar los cambios. Habrá que considerar los efectos
sobre la performance de la aplicación, al adoptar un esquema
menos directo.
Solución
El patrón de Observer, mantiene una lista de objetos interesados
en enterarse de los cambios. Todos los interesados en observar,
deben implementar una interface Observer.
En el diagrama, aparece otra interface, la Subject, que implementan
todos los objetos que quieren ser observados. Al aplicar estas
interfaces, nos independizamos de las clases en concreto, que
más adelante pueden cambiar, sin afectar el esquema adoptado.
Una implementación de este patrón en VB.NET, es la siguiente:
Public Interface Observador
Sub Notificar(ByRef obj As Object)
End Interface
Public Interface Sujeto
Sub Registrar(ByRef eo As Observador)
Sub DesRegistrar(ByRef eo As Observador)
End Interface
Public Class SujetoConcreto : Implements Sujeto
Dim m_Observadores As New Hashtable()
Public Sub Registrar(ByRef eo As Observador) Implements Sujeto.Registrar
m_Observadores.Add(eo, eo)
End Sub
Public Sub DesRegistrar(ByRef ew As Observador) Implements Sujeto.DesRegistrar
m_Observadores.Remove(ew)
End Sub
Public Function Procesar()
Dim ProcesarTiempo As DateTime = DateTime.UtcNow
Dim unObservador As Observador
For Each unObservador In m_Observadores.Keys
unObservador.Notificar(ProcesarTiempo)
Next
End Function
End Class
Public Class EscuchadorUno : Implements Observador
Sub Notificar(ByRef obj As Object) Implements Observador.Notificar
Console.WriteLine("EscuchadorUno procesa " & obj)
End Sub
End Class
Public Class EscuchadorDos : Implements Observador
Sub Notificar(ByRef obj As Object) Implements Observador.Notificar
Console.WriteLine("EscuchadorDos procesa " & obj)
End Sub
End Class
Pero resulta algo notable, que en .NET, tengamos otra forma
de implementar el patrón, que ha nacido de la capacidad de
definir eventos, y escuchadores ("handlers") para esos eventos.
Esa capacidad ya estaba presente de alguna forma en Visual Basic
"normal", pero la implementación .NET es más flexible: más
de un objeto puede escuchar un evento.
La siguiente solución, apela al uso de eventos, y delegados.
Public Class Articulo
Delegate Sub DelegadoCambiaPrecio(ByVal unPrecio As Object)
Public Event CambiaPrecio As DelegadoCambiaPrecio
Dim _cambiaPrecio As Object
Public WriteOnly Property Precio()
Set(ByVal value As Object)
_cambiaPrecio = value
RaiseEvent CambiaPrecio(_cambiaPrecio)
End Set
End Property
End Class
Public Class ArticuloObservador
Public Sub Notify(ByVal unObjecto As Object)
Console.WriteLine("El nuevo precio es:" & unObjecto)
End Sub
End Class
Otros tipos de patrones
Microsoft, reconociendo la importancia de los patrones, ha
publicado sus ideas y sugerencias sobre el tema. En los enlaces
al final del artículo, encontraremos mayor información. Algo
que podemos destacar ahora, es su clasificación, construída
desde un punto de vista distinto. Veamos su marco de patrones:
Según Microsoft, las filas representan niveles progresivos
de abstracción: arquitectura, diseño e implementación, mientras
que las columnas son "puntos de vista" de la solución, perspectivas
como base de datos, aplicación, instalación, infraestructura.
Entonces, según este esquema, hay más que "design patterns": los hay
también de arquitectura y de implementación.
Un patrón de arquitectura, según Buschmann, es entonces:
"Un patrón que expresa un esquema de organización estructural
fundamental para un sistema de software. Provee un conjunto
predefinido de subsistemas, especifica sus responsabilidades,
e incluye reglas y guías para organizar las relaciones entre
ellos".
Discutamos un ejemplo y su patrón, en los siguientes párrafos.
Niveles de abstracción
Si bien la mayor parte de los patrones tiene su aplicación
directa en código, no significa que sea el único nivel al
que se manejan. Comentemos un ejemplo del sitio de Microsoft,
que aplica un patrón que es muy conocido por los desarrolladores, especialmente
en los últimos años.
Estamos desarrollando un sistema empresarial, donde queremos
manejar la información de presupuestos para los clientes
de la empresa. Necesitamos conocer los precios internos,
armar el presupuesto, tener en cuenta el inventario de los
productos, y contemplar los datos e historia del cliente
que pide el presupuesto. La información debe estar disponible
para ser consultada por la web, y provenir de distintas
fuentes de datos.
Querríamos organizar la arquitectura del sistema para que sea
flexible, por ejemplo, que contemple las distintas formas de
acceder a los datos, y las formas de procesarlos , y por último,
los distintos modos de visualizar el resultado.
Así planteado, es un especificación muy general, pero que nos
sirve para presentar un patrón conocido de arquitectura: el
patrón de capas.
Podemos tomar la decisión de separar la aplicación según
este esquema:
Esta solución termina aplicando el patrón Layers, que tanta
veces hemos encontrado (hasta se utiliza en la arquitectura
de redes, donde el modelo ISO se basa justamente en dividir
el trabajo en las capas de transporte, aplicación y otras).
Formalizando la definición del patrón, podemos encontrar:
Contexto
Estamos trabajando en un sistema grande y complejo. Y queremos
manejar la complejidad via la descomposición.
Problema
Cómo estructurar la información para soportar los requerimientos
de mantenimiento, reusabilidad, escalabilidad, y robustez.
Solución
Componer la solución en una serie de capas. Cada capa debe
ocuparse de un nivel del problema, y debe tener poca cohesión
con las demás.
Este patrón tiene consecuencias no evidentes: el cambio en una
capa, debería alterar en poco los cambios en las otras capas. La
prueba ácida, en nuestro ejemplo original, podría ser: al cambiar
las fuentes de datos (las bases de datos usadas), poco debería
afectar a la capa de presentación. Y si el día de mañana queremos
incorporar una capa de presentación distinta (como un sistema
de atención automática por teléfono, usando un IVR), no debería
alterar a las otras capas ya desarrolladas. En definitiva, como
en el ejemplo del modelo ISO de redes, el cambiar la implementación
de una capa, debe tener los mínimos efectos en el resto de la
aplicación.
Si bien en este ejemplo, las capas que elegimos armar son
las de presentación, negocio y datos, el patrón es lo suficientemente
general para aplicarse en otras situaciones.
Y es un patrón de arquitectura: nos da una base para
el fundamento estructural de una aplicación.
Patrones de Integración
En el mundo actual de la informática, pocos sistemas viven en solitario. Cada vez más es la regla, encontrarse con aplicaciones disímiles, de distintos fabricantes, en distintas plataformas, y que no se conectan entre sí. Puede ser que el CRM de la empresa, haya sido comprado a un proveedor de IT, que está totalmente desconectado del sistema empresarial, y éste a su vez, no se comunica con el sitio web de la compañía.
De nuevo, como en los problemas de diseño y de arquitectura, los patrones acuden a la ayuda. Han comenzado a surgir los llamados "integration patterns"; o patrones de integración.
En el sitio www.integrationpatterns.com se comentan más de 60 patrones descubiertos, que ayudan a la hora de armar soluciones que integren a los sistemas empresariales. Entre los temas cubiertos, se encuentran:
Estilos de integración
Cuáles son las formas de comunicar sistemas: desde el viejo truco del archivo compartido (el sistema A graba información en un archivo, y se lo entrega al sistema B), hasta la base de datos compartida, la llamada a procedimientos remotos (ahora en .NET tenemos Web Services y Remoting), y el uso de sistemas de mensajería.
Patrones de Canal
Describen los patrones adoptados en la implementación de canales: los medios de comunicación que se emplean. Por ejemplo, el canal punto-a-punto, donde la comunicación es de una aplicación a otra establecida, versus el canal-suscripto, donde las aplicaciones interesadas se suscriben a un canal de información que otra aplicación produce.
Patrones de Construcción de Mensajes
Cuáles son los métodos de construcción de mensajes, que en definitiva, son los datos que viajan en los canales. Desde el mensaje tipo comando (similar al patrón Command), hasta el mensaje tipo Consulta (Query), pasando por el mensaje tipo Documento. O el mensaje tipo Respuesta. Todas estas formas de mensajes y su construcción ya han sido implementadas en sistemas de mensajería, y han surgido patrones de soluciones.
Patrones de Ruteo
Cómo se pueden encaminar los mensajes, desde su origen hasta su destino? De nuevo aparecen patrones, como el Filtro de Mensajes, que permite clasificar los mensajes de un canal, o el Ruteador Dinámico, que establece a qué destino se envía un mensaje, en base a condiciones de la red (por ejemplo, para implementar balanceo de carga en los servidores destinos).
Es un tema apasionante, que esperamos tratar en próximos artículos. En el ámbito local, Lagash, y Microsoft Consulting de Argentina, han desarrollado, en base a estos patrones, el MBI (Microsoft Business Integrator).
Patrones en Capas, Conclusión
Cuando desarrollamos aplicaciones distribuidas (o aunque no lo sean, cuando separamos la lógica en capas), podemos aplicar tanto los patrones de diseño como de arquitectura, en múltiples ocasiones. Es probable, que ya estemos aplicando alguno de los mencionados. Por ejemplo, es común, al implementar una capa de abstracción de datos, encapsular los cambios de la base SQL adoptada, en clases factoría, que se encargan de establecer la conexión con el servidor de datos. En el código completo del artículo, encontraremos ejemplos de este tipo.
En la capa de negocios, donde el cambio puede afectar más a los demás elementos del sistema, podemos adoptar patrones de conducta, como el Strategy o el Decorator, para poder ada
ptarnos a las modificaciones de las reglas de proceso.
Y cuando interviene la capa de presentación, la conocida separación entre Modelo y Vista, se puede aplicar. De esta forma, el Modelo, podemos reusarlo desde distintas vistas, ya sea un formulario Windows, o una página Web.
Esperamos en próximos artículos, encarar temas como:
- Diseño de una capa de abstracción de datos
- Separación del Modelo de la Vista, para construir una aplicación con distintas interfaces
- Aplicación de patrones de integración, para comunicar aplicaciones disímiles
Como vemos, el tema patrones inunda todas las etapas del análisis, diseño y desarrollo. Es inteligente, al armar una solución, aprovechar lo ya probado.
Enlaces
http://www.microsoft.com/patterns
http://msdn.microsoft.com/practices
Las páginas de Microsoft, donde se describen los patrones, el marco que plantea Microsoft de clasificación, y ejemplos de su uso.
http://www.dofactory.com
Un catálogo con los patrones del libro de "GoF", con implementaciones de los 23 patrones, en C#
http://www.integrationpatterns.com
Excelente colección de patrones de integración.
http://www.martinfowler.com
El sitio de Martín Fowler, con información sobre patrones, metodologías y desarrollo orientado a objetos.
http://patterndigest.com/
Descripción de patrones, y novedades, noticias y foros del tema.
http://c2.com/ppr/index.html
Portland Pattern Repository, páginas sobre patrones en general.
http://hillside.net/patterns/
Patterns Home Page, desde hace años dedicados a las mejores prácticas del uso de patrones
http://www.cs.wustl.edu/~schmidt/patterns.html
Design Patterns, Pattern Languages, and Frameworks, la página de Douglas Schmidt sobre patrones y lenguajes de patrones.
Lecturas
Design Patterns, Addison Wesley, de Gamma, Helm, Johnson, Vlissides, el libro de base sobre los patrones de diseño.
Profesional Design Patterns in VB.NET: Building Adaptable Applications, Wrox, Fischer, Slater, Stromquist, Wu, aplicando los patrones de Gamma en .NET
UML Distilled, Addison-Wesley, Martín Fowler, un resumen sobre UML, contiene una introducción a los patrones.
|