Enero 2003

Patterns en .NET


Tema: .NET Nivel: Intermedio

Angel "Java" López

El desarrollo de software es una actividad humana compleja. Otras ciencias o artes, no abarcan tantos niveles distintos de detalle, como la creación de programas. Actualmente, con el nivel de exigencia de los usuarios, y la plétora de tecnologías disponibles, cada sistema, pequeño o grande, nos demanda una cantidad de esfuerzo, que es necesario apelar a cada herramienta y habilidad, para obtener un mejor resultado.

Si ha estado en este trabajo un tiempo, habrá aprendido a reutilizar su código, y tendrá su marco de trabajo básico para usar de base para nuevos proyectos. No es el único. A lo largo de la historia de la programación, han aparecido recomendaciones y metodologías, para llevar a buen puerto el análisis, diseño e implementación de sistemas. Ya podemos recordar la programación estructurada, y la más "nueva" orientación a objetos.

Una de esas corrientes, es el uso de patrones. Estudiemos en este artículo, que es un patrón, y cómo se aplica en particular en la nueva tecnología .NET.

Una breve historia

Es curioso que el concepto de patrón haya aparecido en la arquitectura (la de construcción de edificios). Fue el arquitecto Christopher Alexander quien estudió la idea de patrón, en el contexto de construcción de edificios y comunidades. Él escribió, ya en 1977:
Cada patrón 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 se refería a la aplicaciones de patrones en arquitectura, pero notamos que se puede aplicar perfectamente a la ingeniería de software. Podemos usar objetos y clases, en lugar de paredes, espacios y edificios, pero la idea básica es la misma.

A fines de los ochenta, esta idea de patrones de software, fue tomando forma. El término "software architecture" comenzó a popularizarse. En conferencias y reuniones, los especialistas del tema fueron incubando la idea de un manual para arquitectos de sistemas, una especie de guía de mejores prácticas, que ya se habían comenzado a utilizar.

Ken Beck, de la comunidad de Smalltalk, el ambiente de objetos, difundió las ideas de Alexander, adaptándolas a la arquitectura de sistemas, desde una columna de "Smalltalker Report". Peter Coad coleccionaba soluciones ya encontradas a problemas de diseño, como son los patrones, como menciona en un artículo suyo de 1992, publicado en las comunicaciones de la ACM.

Pero es con el libro de Gamma, Helm, Johnson y Vlissides, el "Gang of Four", titulado "Design Patterns" (Patrones de Diseño), con el que aparece con fuerza el concepto. Catalogan una serie de patrones de diseño, que hasta hoy son la base de otras clasificaciones. Ese libro se ha convertido en casi un objeto de culto, y es la referencia obligada del tema que nos ocupa.

¿Qué es un Design Pattern?

Pero basta ya de introducción a la historia, y definamos qué es un patrón de diseño (hay otros tipos de patrones, como los de arquitectura, y de integración).

Lo fundamental, que es una SOLUCION a un PROBLEMA. Y que esa solución encontrada, ya ha sido usada en varios proyectos reales. Y no es entonces un trozo de código, o una librería, o un marco de trabajo: es una descripción de una solución, cuya implementación puede variar de entorno a entorno, o dependiendo del lenguaje de implementación o tecnología. Se describe entonces con:

  • El nombre: Todo patrón, para ser referido en otros estudios, tiene un nombre. A veces, la elección de este nombre no ha sido fácil. Sucede con los patrones nuevos, que más de un grupo lo bautiza de distinta forma. Pero con el tiempo, uno de los nombres designados triunfa entre la comunidad.

  • El problema: Describe el problema original a solucionar. Puede incluir desde detalles específicos, aunque se prefiere una descripción general que pueda aplicarse en varios contextos.

  • La solución: Como mencionamos, éste es el elemento importante del patrón. Enuncia un esquema de solución al problema, que puede implementarse de distintas formas.

  • Las consecuencias: En todo problema y solución, se presentan distintas fuerzas. Cada solución tiene sus pro y sus contra. A veces, para el mismo problema, hay más de un patrón aplicable.

Tenemos que destacar que en cada patrón, hay entonces un problema y una solución. Y un patrón no es adoptado por la comunidad, y no merece ese nombre, hasta que no es aplicado exitosamente en sistemas en funcionamiento. No se aceptan patrones teóricos: cada uno debe estar respaldado por casos de éxito reales. 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.

Clasificación de Patrones

Gamma y sus colaboradores se concentran en los patrones de diseño y los clasifican en las siguientes categorías.

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.

Es un ejemplo del patrón Abstract Factory, que nos da una interface para crear objetos de alguna familia, sin especificar la clase en concreto.

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.

Otro de los clásicos, es el Fachada o Facade, que provee una interface unificada a un conjunto de funciones del sistema.

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.

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

Describamos un caso de patrón, como se estila en la literatura.

Contexto:

Necesitamos que alguna funcionalidad se acceda desde el resto del sistema. Y es preciso que esos datos sean únicos: un solo objeto o instancia que presente esa funcionalidad. Por ejemplo, un objeto que sea el que se comunique con el Sistema Operativo, para lanzar procesos. O un objeto que presente las funciones necesarias para manejar un Sistema Contable, y que sea el punto de entrada a un sistema externo al nuestro.

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:

Figura 1: Diagrama UML

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 Constructor
   Private Sub New()
      mTime = Now.ToLongTimeString
      MsgBox("New Singleton Object")
   End Sub

   Public Shared Function GetInstance() As Singleton
      If mInstance Is Nothing Then
         mInstance = New Singleton()
      End If

      Return mInstance
   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 Constructor
   Private Sub New()
      mTime = Now.ToLongTimeString
      MsgBox("New Singleton Object")
   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
End Class

Veamos otra implementación, esta vez en C#.

using System;

public class Singleton
{
   private static Singleton instance;
   private Singleton() {}
   public static Singleton Instance
   {
     get 
     {
       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 Singleton
{
   private static readonly Singleton instance = new Singleton();
   private Singleton(){}
   public static Singleton Instance
   {
     get 
     {
       return instance; 
     }
   }
}

Demos ahora un ejemplo, de un patrón, usado en un ejemplo de la propia empresa de Redmond.

Figura 2: Sistema original

El patrón Fachada

Este un patrón que Microsoft recomienda de alguna forma en sus guías de arquitectura, y sus ejemplos. En qué consiste?

Consideremos un sistema que tenga una capa de presentación (formularios Windows o páginas ASP.NET), y necesite comunicarse con clases de la capa de negocios.

Notamos a simple vista, que hay mucha interacción entre los objetos de las dos capas. La comunicación, con el tiempo, se puede ir complicando. Y cualquier cambio en los objetos de la capa de negocios, puede afectar a varios métodos de la capa de presentación.

En nuestro auxilio acude el patrón de Fachada. Consiste en construir una clase que se interponga entre las clases clientes y las clases de servicios, como se muestra en el esquema.

Figura 3: Usando el Patrón

Y es justamente, es la solución adoptada en varios ejemplos de Microsoft, como el nuevo Duwamish 7.

Fachada en Duwamish

El Duwamish 7 implementa un sitio web, con ASP.NET, con consultas de un catálogo de libros, un carrito de compras, la toma del pedido, y los datos del cliente.

El código fuente esta disponible en el sitio de Microsoft (ver referencias) o viene incluido con el Visual Studio .NET.

Si abrimos la solución en el Visual Studio.NET, encontramos la estructura:

Vemos que hay varios proyectos. ¿Y dónde está aplicado entonces el patrón que tenemos entre manos?

Pues en el primer proyecto (BusinessFacade) encontraremos que los autores del ejemplo usaron el patrón de Fachada.

No se usó una sola clase, sino que cada subsistema (Clientes, Ordenes de Pedido, Productos) tiene una propia.

Examinemos un resumen del código del sistema de Clientes:

Namespace Duwamish7.BusinessFacade
   Public Class CustomerSystem
      Inherits MarshalByRefObject

      Public Function GetCustomerByEmail( _
         ByVal emailAddress As String, _
         ByVal password As String) _
         As CustomerData
         '
         ' Obtiene los datos de un cliente
         '
      End Function
      
      Public Function UpdateCustomer( _
         ByVal customer as CustomerData) _
         As Boolean
         '
         ' Actualiza un cliente
         '
      End Function
      
      Public Function CreateCustomer( _
         ByVal emailAddress As String, _
         ByVal password As String, _
         ByVal name As String, _
         ByVal address As String, _
         ByVal country As String, _
         ByVal phoneNumber As String, _
         ByVal fax As String, _
         ByRef custData As CustomerData) As Boolean
         '
         ' Crea un nuevo cliente
         '
      End Function
   End Class 
End Namespace

Esta es la funcionalidad que la capa de presentación necesita: obtener los datos de un cliente, dado su email, o actualizar los datos de un cliente. Los objetos de la capa cliente, solamente necesitan conocer esta interface de servicio, esta clase que implementa el patrón Fachada. Cuando cambie la forma de implementar la actualización de un cliente, ya sea porque cambie la base de datos, o los procesos de validación de datos, todos esos cambios quedan encapsulados, ocultos, debajo de la fachada, resguardando a la presentación de esas mutaciones.

Conclusión

El tema patrones se usa desde el siglo pasado, en el desarrollo de software. Y con el nuevo .NET, la llegada a la madurez de la tecnología Microsoft (al fin herencia en todo, interfaces, clase, objetos, y una librería de clases base impresionante), y la necesidad de construir aplicaciones más complejas, se impone el estudio del tema. Recomiendo revisar los documentos de Microsoft, y de otras tecnologías, así como aprender de los ejemplos. Espero que encuentren, como yo, placer en su estudio, y utilidad en su aplicación.

Referencias

  • Todo sobre patrones, según Microsoft
  • Artículos y recursos sobre arquitectura de aplicaciones
  • Enterprise Solution Patterns Using Microsoft .NET
  • Ejemplos de aplicaciones distribuidas, con código completo.
    Aquí encontraremos el Duwamish 7, el ColdStorage, el PetShop y otros.
  • Explicación del uso del patrón en otra tecnología: Java.
  • Building Distributed Applications,
    las recomendaciones de Microsoft a aplicar en la construcción de este tipo de aplicaciones.
  • Un catálogo con los patrones del libro de "GoF", con implementaciones de los 23 patrones, en C#
  • Descripción de patrones, y novedades, noticias y foros del tema.
  • Portland Pattern Repository, páginas sobre patrones en general.
  • Patterns Home Page, desde hace años dedicados a las mejores prácticas del uso de patrones
  • Excelente colección de patrones de integración.
  • El sitio de Martín Fowler, con información sobre patrones, metodologías y desarrollo orientado a objetos.

Angel "Java" López (Buenos Aires - Argentina) es Microsoft MVP (Most Valuable Professional). Posee más de veinte años de experiencia en el diseño y desarrollo de software trabajando en diferentes plataformas y lenguajes. Se especializa en desarrollo de intranets y de sitios web, empleando distintas tecnologías. Actualmente se desempeña como Líder de la Comunidad de desarrolladores J# del Grupo de Usuarios Microsoft, donde dicta cursos y jornadas periodicamente. Trabaja también como docente de programación en Java y .NET. Puede encontrarlo en http://www.ajlopez.com/.