miércoles, 20 de junio de 2007

Serialización en C#

La clase siguiente tiene todas las funciones básicas para serializar.

Pasando un objeto a Serialize te devuelve un string con el mismo serializado.
Si usas Deserialize todo lo que tenes que hacer es pasarle el tipo y el string para que te levante el objeto de nuevo.



 
/// <summary>
/// Clase que contiene todas las funciones de serialización básicas.
/// </summary>
public class Serializacion
{
#region -- Binary Serialization --

/// <summary>
/// Serializa un objeto a un archivo binario.
/// </summary>
/// <param name="fileName">Ruta del archivo en el cual depositar los bytes</param>
/// <param name="objToSerialize">Objeto en memoria a transformar en bytes</param>
public static void SerializeToBin(string fileName, object objToSerialize)
{
if (objToSerialize == null)
{
throw new Exception("El objeto a serializar no debe ser nulo.");
}

// Creo el archivo de destino y lo devuelvo a un fileStream
FileStream fs = File.Create(fileName);

// Creo un BinaryFormatter que me sirve como serializador
BinaryFormatter serializer = new BinaryFormatter();

// Le digo al serializador que guarde los bytes en un archivo
// binario, representado por el FileStream
serializer.Serialize(fs, objToSerialize);

// Cierro el FileStream
fs.Close();
}

/// <summary>
/// Deserializa un objeto a partir del contenido de un archivo binario
/// </summary>
/// <param name="fileName">Archivo desde donde toma los bytes que se
/// encuentran serializados</param>
/// <returns>objeto deserializado</returns>
public static object DeserializeFromBin(string fileName)
{
if (!File.Exists(fileName))
{
throw new Exception("El archivo de origen para deserializar " +
"no existe. No se encuentra la ruta '" + fileName + "'");
}

// Abro el archivo de origen y lo devuelvo a un fileStream
FileStream fs = File.OpenRead(fileName);

// Creo un BinaryFormatter que me sirve como deserializador
BinaryFormatter deserializer = new BinaryFormatter();

// Le digo al deserializador que me devuelva el objeto a partir
// del FileStream
object objDeserialized = deserializer.Deserialize(fs);

// Cierro el FileStream
fs.Close();

// Devuelvo el objeto deserializado
return objDeserialized;
}

#endregion

#region -- Xml Serialization using DataSet --

/// <summary>
/// Serializa a un XML y lo coloca en un DataSet
/// </summary>
/// <param name="pObject">Objeto a serializar</param>
/// <param name="pDataSet">Dataset donde se coloca el XML</param>
public static void SerializeToXml(object pObject, ref DataSet pDataSet)
{
XmlSerializer wSerializer;
MemoryStream wStream = new MemoryStream();

if (pDataSet == null)
pDataSet = new DataSet();

wSerializer = new XmlSerializer(pObject.GetType());

wSerializer.Serialize(wStream, pObject);

wStream.Position = 0;
pDataSet.ReadXml(wStream);
}

/// <summary>
/// Serializa un objeto
/// </summary>
/// <param name="pObj"></param>
/// <param name="pDataSet"></param>
public static void Serialize(object pObj, ref DataSet pDataSet)
{
XmlSerializer serializer;
MemoryStream ms = new MemoryStream();

if (pDataSet == null)
pDataSet = new DataSet();

serializer = new XmlSerializer(pObj.GetType());
serializer.Serialize(ms, pObj);

ms.Position = 0;
pDataSet.ReadXml(ms);
}
/// <summary>
/// Deserializa un objeto a partir de un DataSet
/// </summary>
/// <param name="pObjType"></param>
/// <param name="pDataSet"></param>
/// <param name="pTableName"></param>
/// <returns></returns>
public static object Deserialize(Type pObjType, DataSet pDataSet, string pTableName)
{
XmlDocument wDom = new XmlDocument();
wDom.LoadXml(pDataSet.GetXml());
return Deserialize(pObjType, wDom.GetElementsByTagName(pTableName).Item(0).OuterXml);
}

#endregion

#region -- Xml Serialization using Xml --

/// <summary>
/// Deserializa un objeto.
/// </summary>
/// <param name="pObjType"></param>
/// <param name="pXmlData"></param>
/// <returns></returns>
public static object Deserialize(Type pObjType, string pXmlData)
{
XmlSerializer wSerializer;
UTF8Encoding wEncoder = new UTF8Encoding();
MemoryStream wStream = new MemoryStream(wEncoder.GetBytes(pXmlData));

wSerializer = new XmlSerializer(pObjType);
return wSerializer.Deserialize(wStream);
}

/// <summary>
/// Deserializa un objeto.
/// </summary>
/// <param name="pObjType"></param>
/// <param name="pXmlData"></param>
/// <param name="pXPath"></param>
/// <returns></returns>
public static object Deserialize(Type pObjType, string pXmlData, string pXPath)
{
XmlDocument wDom = new XmlDocument();
wDom.LoadXml(pXmlData);
return Deserialize(pObjType, wDom.DocumentElement.SelectSingleNode(pXPath).OuterXml);
}

/// <summary>
/// Deserializa un objeto
/// </summary>
/// <param name="pTipo"></param>
/// <param name="pXml"></param>
/// <returns></returns>
public static object DeserializeFromXml(Type pTipo, string pXml)
{
XmlSerializer wSerializer;
StringReader wStrSerializado = new StringReader(pXml);
XmlTextReader wXmlReader = new XmlTextReader(wStrSerializado);
XmlSerializerNamespaces wNameSpaces = new XmlSerializerNamespaces();
object wResObj = null;

wNameSpaces.Add("", "");
wSerializer = new XmlSerializer(pTipo);
wResObj = wSerializer.Deserialize(wXmlReader);

return wResObj;
}

/// <summary>
/// Serializa un objeto
/// </summary>
/// <param name="pObj"></param>
/// <returns></returns>
public static string SerializeToXml(object pObj)
{
XmlSerializer wSerializer;
StringWriter wStwSerializado = new StringWriter();
XmlTextWriter wXmlWriter = new XmlTextWriter(wStwSerializado);
XmlSerializerNamespaces wNameSpaces = new XmlSerializerNamespaces();

wXmlWriter.Formatting = Formatting.Indented;
wNameSpaces.Add("", "");
wSerializer = new XmlSerializer(pObj.GetType());
wSerializer.Serialize(wXmlWriter, pObj, wNameSpaces);

return wStwSerializado.ToString().Replace("<?xml version=\"1.0\" encoding=\"utf-16\"?>", "");
}

/// <summary>
/// Serializa un objeto.
/// </summary>
/// <param name="pObj">Objeto a serializar</param>
/// <returns>Representación en XML del objeto</returns>
public static string Serialize(object pObj)
{
return Serialize(pObj, false);
}

/// <summary>
/// Serializa un objeto.
/// </summary>
/// <param name="pObj">Objeto a serializar</param>
/// <param name="pRemoveDeclaration">Indica si se debe remover el nodo de declaración</param>
/// <returns>Representación en XML del objeto</returns>
public static string Serialize(object pObj, bool pRemoveDeclaration)
{
XmlDocument wDoc = new XmlDocument();
wDoc.Load(GetStream(pObj));

if (pRemoveDeclaration && wDoc.ChildNodes.Count > 0 && wDoc.FirstChild.NodeType == XmlNodeType.XmlDeclaration)
{
wDoc.RemoveChild(wDoc.FirstChild);
}

return wDoc.InnerXml;
}


/// <summary>
/// Devuelve un stream formado a partir del objeto enviado por parámetro.
/// </summary>
/// <param name="pObj">Objeto para extraer stream</param>
/// <returns>MemoryStream</returns>
public static MemoryStream GetStream(object pObj)
{
XmlSerializer wSerializer;
MemoryStream wStream = new MemoryStream();

wSerializer = new XmlSerializer(pObj.GetType());
wSerializer.Serialize(wStream, pObj);

wStream.Position = 0;

return wStream;
}

#endregion
}

martes, 19 de junio de 2007

El Patrón Abstract Factory

Este patrón es útil cuando se necesita instanciar clases complejas y devuelven varios objetos en conjunto.

A continuación voy a presentar un ejemplo muy simple de este patrón.




using System;

using System.Collections.Generic;

using System.Text;



namespace carpinteriaFactory

{

public enum tipoTabla

{

Cuadrada,

Redonda,

Ovalada

}

class tabla

{

private tipoTabla _tipo = tipoTabla.Cuadrada;



public tipoTabla Tipo

{

get { return _tipo; }

set { _tipo = value; }

}



public tabla(tipoTabla tipo)

{

Tipo = tipo;

}

}



public enum tipoPata

{

Cuadrada,

Redonda

}

class Pata

{

private tipoPata _tipo = tipoPata.Redonda;



public tipoPata Tipo

{

get { return _tipo; }

set { _tipo = value; }

}



public Pata(tipoPata tipo)

{

this.Tipo = tipo;

}

}



public class Mueble

{



}



class Mesa : Mueble

{

private Pata[] _Patas;



public Pata[] Patas

{

get { return _Patas; }

set { _Patas = value; }

}



private tabla _tabla;



public tabla Tabla

{

get { return _tabla; }

set { _tabla = value; }

}



private String _nombre;



public String Nombre

{

get { return _nombre; }

set { _nombre = value; }

}



public Mesa() { }



public Mesa(String nombre, tipoTabla tabla, tipoPata TipoPata, Int32 CantidadPatas)

{

this.Nombre = nombre;



this.Tabla = new tabla(tabla);



Pata[] wPatas = new Pata[CantidadPatas];



for (int i = 0; i < CantidadPatas; i++)

{

wPatas[i] = new Pata(TipoPata);

}



this.Patas = wPatas;

}



public override string ToString()

{

String r = Nombre + " => " + Tabla.Tipo.ToString();

Int32 i = 1;

foreach (Pata p in Patas)

{

r = r + " Pata " + i.ToString() + ": " + p.Tipo.ToString();

i++;

}

return r;

}



}



public abstract class CarpinteriaFactory

{

abstract public Mueble crearMueble();

}





public class MesaAntiguaFactory : CarpinteriaFactory

{

public override Mueble crearMueble()

{

return new Mesa("Mesa Antigua", tipoTabla.Cuadrada, tipoPata.Cuadrada, 4);

}

}



public class MesaExoticaFactory : CarpinteriaFactory

{

public override Mueble crearMueble()

{

Mesa exotica = new Mesa("Mesa Exótica", tipoTabla.Ovalada, tipoPata.Redonda, 4);

exotica.Patas[1].Tipo = tipoPata.Cuadrada;

exotica.Patas[3].Tipo = tipoPata.Cuadrada;

return exotica;

}

}



public class MesaModernaFactory : CarpinteriaFactory

{

public override Mueble crearMueble()

{

return new Mesa("Mesa Moderna", tipoTabla.Redonda, tipoPata.Redonda, 3);

}

}



}


Como pueden ver existen las clases pata y tabla que tienen propiedades cada una que definen su ámbito.


Luego existe una clase mueble que por el momento no tiene nada pero que se podría anexar funcionalidad.


La clase mesa hereda de mueble y se compone de una tabla y un array de patas, además de un nombre.


Luego existe la clase abstracta carpinteriaFactory que define el método publico crearMueble.


Por último existen tres implementaciones de carpinteríaFactory: MesaExoticaFactory,MesaAntiguaFactory y MesaModernaFactory.


Estas clases instancian la clase mesa, crear todos los objetos necesarios y asignan valores a sus propiedades.


De esta forma se pueden obtener objetos complejos y encapsular su instanciación. Les dejo el esquema del patrón:




lunes, 11 de junio de 2007

MasterPage con ASP.Net 2.0

Este es un video que explica de forma muy básica el uso de MasterPage de ASP.NET 2.0. Tal vez sirva como introducción para los principiantes. ¡Espero que les sirva!

Las señoritas de Avignon



Las señoritas de Avignon es un cuadro de Pablo Picasso. Es un cuadro que a primera vista genera repudio. En realidad puede llegar a parecer feo. Pero lo que hizo este cuedro tan famoso es como el autor plasma los sentimientos de rechazo y astío por aquellas mujeres de forma inconciente.
Este es un cuadro de gran composición que mezcla de forma exiquisita la tradición naturalista y el movimiento abstracto. Demuestra la capacidad pictórica de Picasso y su talento para crear una obra tan revolucionaria para la época.

miércoles, 6 de junio de 2007

Los Patrones Estrategia y Decorador trabajando en conjunto

A continuación daré un ejemplo de como utilizar estos dos patrones de forma conjunta en C#, aunque se podría aplicar en VFP de forma muy sencilla siguiendo la misma lógica que venimos trabajando en los distintos artículos.





Vamos a reutilizar todas las clases que venimos publicando en el artículo de estategia, decorador y puente.




La pantalla anterior es como luce nuestra aplicación. Veamos el código para lograrlo:





using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using estrategia;


namespace WindowsApplication1
{
public partial class CodigoDinamico : Form
{
public CodigoDinamico()
{
InitializeComponent();
}

private Estrategia _Procesador= new Estrategia();

public Estrategia Procesador
{
get { return _Procesador; }
set { _Procesador = value; }
}

private void asignarFuncionalidad()
{
Lis<String> Items = new List<String>();
if (optApeNom.Checked) { Items.Add("Puente"); }
if (optNomApe.Checked) { Items.Add("NombreApellido"); }
if (optEnriquecido.Checked) { Items.Add("Enriquecido"); }
if (chkNumera.Checked) { Items.Add("AgregarNumeracion"); };
if (chkNacion.Checked) { Items.Add("AgregarNacionalidad"); };
Procesador = Dinamico.Proceso(Items);
if (chkNacion.Checked)
{
AgregarNacionalidad Nacionalidad = (AgregarNacionalidad)Procesador.Contexto.MiClase;
Nacionalidad.Nacionalidad = txtNacion.Text;
}

}

private void btn1_Click(object sender, EventArgs e)
{
asignarFuncionalidad();
txtSalida.Text = this.Procesador.ProcesarNombres(txtNombre.Text, txtApellido.Text);
}

private void chkNacion_CheckedChanged(object sender, EventArgs e)
{
if (chkNacion.Checked)
{
txtNacion.ReadOnly = false;
}
else
{
txtNacion.ReadOnly = true;
}
}


}
}
 
Como verán, en el botón btn1 siempre llamamos a asignarFuncionalidad antes de llamar a nuestra función. Esta función crea una lista genérica de String y se la pasa al metódo Estático "Proceso" de la Clase Dinamico. Veamos esta nueva clase:

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace estrategia
{
public class Dinamico
{
public static Estrategia Proceso(List<String> Decoradores)
{
Assembly Ass = Assembly.GetExecutingAssembly();
Object Estra = Ass.CreateInstance("estrategia." + Decoradores[0].Trim());
Decoradores.Remove(Decoradores[0]);
Object[] o = new Object[1];
o[0] = Estra;
foreach (String deco in Decoradores)
{
o[0] = Ass.CreateInstance("estrategia." + deco, false, BindingFlags.CreateInstance,null, o, null, null);
}
Estrategia st = new Estrategia(new contexto((IProcesarNombres)o[0]));
return st;
}
}
}
La función genera la estrategia y los decoradores mediante Reflection.
Con el primer elemento de la lista selecciona la estrategia y con el resto agrega los decoradores. De esta forma se puede crear dinámicamente toda la funcionalidad. 
Si se crea una nueva clase solo se debe para el nombre de la misma en la lista genérica y sale funcionando sin tocar casi nada del código.
Este tipo de tecnología tiene muchas aplicaciones y que seguro ustedes ya deben estar pensando.

martes, 5 de junio de 2007

El Patrón Estrategia en VFP

Para implementar el patrón estrategia en Visual Fox Pro vamos a tomar el mismo ejemplo que trabajamos con C#.

Debemos crear el contexto, la estrategia abstracta, etc, etc...

Veamos un poco de código:




*Interfaz

DEFINE CLASS IComponente as Custom

FUNCTION procesarNombres()

LPARAMETERS nombre,apellido

endfunc

ENDDEFINE





*estrategia concreta

DEFINE CLASS puente as Icomponente



Caracter= ", "



PROCEDURE Caracter_ACCESS

RETURN THIS.Caracter

ENDPROC



PROCEDURE Caracter_ASSIGN

LPARAMETERS Valor

THIS.Caracter=Valor

ENDPROC



FUNCTION procesarNombres()

LPARAMETERS nombre,apellido

RETURN ALLTRIM(Apellido) + this.Caracter + ALLTRIM(nombre)

ENDFUNC



ENDDEFINE



*estrategia concreta

DEFINE CLASS NombreApellido as Icomponente



Caracter= " "



PROCEDURE Caracter_ACCESS

RETURN THIS.Caracter

ENDPROC



PROCEDURE Caracter_ASSIGN

LPARAMETERS Valor

THIS.Caracter=Valor

ENDPROC



FUNCTION procesarNombres()

LPARAMETERS nombre,apellido

RETURN ALLTRIM(nombre) + this.Caracter + ALLTRIM(Apellido)

ENDFUNC



ENDDEFINE



*estrategia concreta

DEFINE CLASS Enriquecido as Icomponente



Caracter= " - "



PROCEDURE Caracter_ACCESS

RETURN THIS.Caracter

ENDPROC



PROCEDURE Caracter_ASSIGN

LPARAMETERS Valor

THIS.Caracter=Valor

ENDPROC



FUNCTION procesarNombres()

LPARAMETERS nombre,apellido

RETURN "Apellido: " + ALLTRIM(Apellido) + this.Caracter + "Nombre: " + ALLTRIM(nombre)

ENDFUNC



ENDDEFINE



*clase de contexto

DEFINE CLASS contexto as Custom





MiClase= null



PROCEDURE MiClase_ACCESS

RETURN THIS.MiClase

ENDPROC



PROCEDURE MiClase_ASSIGN

LPARAMETERS Valor

THIS.MiClase=Valor

ENDPROC



PROCEDURE init()

LPARAMETERS Clase

This.MiClase=Clase

ENDPROC



ENDDEFINE



*estrategia abstracta

DEFINE CLASS Estrategia as Icomponente



contexto= " "



PROCEDURE contexto_ACCESS

RETURN THIS.contexto

ENDPROC



PROCEDURE contexto_ASSIGN

LPARAMETERS Valor

THIS.contexto=Valor

ENDPROC



PROCEDURE init()

LPARAMETERS Clase

This.contexto=Clase

ENDPROC



FUNCTION procesarNombres()

LPARAMETERS nombre,apellido

RETURN this.contexto.MiClase.procesarNombres(nombre,apellido)

ENDFUNC



ENDDEFINE






Se agregaron dos estrategias además de Puente: NombreApellido y Enriquecido. Las misma realizan distintas acciones para tener de ejemplo.





Veamos ahora como utilizar esto en un formulario:



Al presionar cada botón estamos utilizando una estrategia distinta.

El código del primer botón:


Estrategia = NEWOBJECT("Estrategia",'','',NEWOBJECT("contexto",'','',NEWOBJECT("NombreApellido")))
ThisForm.txt1.Value=Estrategia.procesarnombres(thisform.txtNombre.Value,thisform.txtApellido.Value)
RELEASE Estrategia

El código del segundo botón:


Estrategia = NEWOBJECT("Estrategia",'','',NEWOBJECT("contexto",'','',NEWOBJECT("Puente")))
ThisForm.txt2.Value=Estrategia.procesarnombres(thisform.txtNombre.Value,thisform.txtApellido.Value)
RELEASE Estrategia

El código del tercer botón:


Estrategia = NEWOBJECT("Estrategia",'','',NEWOBJECT("contexto",'','',NEWOBJECT("Enriquecido")))
ThisForm.txt3.Value=Estrategia.procesarnombres(thisform.txtNombre.Value,thisform.txtApellido.Value)
RELEASE Estrategia

En el init del formulario:


SET PROCEDURE TO "estrategia.prg"

donde "estrategia.prg" es el archivo donde están escritas las clases.

Cambiar de una estrategia a otra es muy simple. En entornos donde la tasa de cambios es muy alta este tipo de metodología es excelente.

En las siguientes publicaciones veremos cómo utilizar varios patrones a la vez.-

El Patrón Estrategia en C#

Vamos a ver ahora como implementar el patrón estrategia en c#.

Vamos a reutilizar las clases que venimos trabajando con el puente y el decorador. Pues esa es la idea de los patrones ¿no?. Vamos a reutilizar, entonces, la interfaz "procesarNombres" y la Clase "Puente" dentro del namespace estrategia.

Vamos a crear la clase de contexto:



using System;

using System.Collections.Generic;

using System.Text;



namespace estrategia

{

public class contexto

{

IProcesarNombres _MiClase;



public IProcesarNombres MiClase

{

get { return _MiClase; }

set { _MiClase = value; }

}



public contexto(IProcesarNombres Clase)

{

MiClase = Clase;

}

}

}
 
Ahora crearemos la estrategia abstracta que implementa IProcesarNombres.
 



using System;

using System.Collections.Generic;

using System.Text;



namespace estrategia

{

public class Estrategia : IProcesarNombres

{

private contexto _contexto;



public contexto Contexto

{

get { return _contexto; }

set { _contexto = value; }

}



public Estrategia(contexto clase)

{

Contexto = clase;

}



public Estrategia()

{

}



public void AsignarContexto(contexto clase)

{

Contexto = clase;

}



public string ProcesarNombres(string Nombre, string Apellido)

{

return Contexto.MiClase.ProcesarNombres(Nombre, Apellido);

}



}

}
 

Ya teniendo el contexto y la estategia abstracta podemos hacer las estrategias concretas que también implementan IProcesarNombres. 
Vamos a utlizar aquí la clase puente que ya implementa esta interfaz y además vamos a crear dos estrategias más: Una que muestre nombre y apellido, y otra que Muestre 'Apellido:' y 'Nombre:' y sus respectivos valores.



using System;

using System.Collections.Generic;

using System.Text;



namespace estrategia

{

public class NombreApellido:IProcesarNombres

{

private String _caracter = " ";



public String Caracter

{

get { return _caracter; }

set { _caracter = value; }

}



public string ProcesarNombres(string Nombre, string Apellido)

{

return Nombre.Trim() + Caracter + Apellido.Trim();

}

}
}





using System;

using System.Collections.Generic;

using System.Text;



namespace estrategia

{

public class Enriquecido:IProcesarNombres

{

private String _caracter = " - ";



public String Caracter

{

get { return _caracter; }

set { _caracter = value; }

}



public string ProcesarNombres(string Nombre, string Apellido)

{

return "Apellido: " + Apellido.Trim() + Caracter + "Nombre: " + Nombre.Trim();

}

}

}
Ahora sólo nos falta ver como deberíamos utilizarlo. En la siguiente figura se muestra el formulario.
 
El código que contiene es el siguiente:



using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using estrategia;



namespace WindowsApplication1

{

public partial class Form1 : Form

{

public Form1()

{

InitializeComponent();

}



private void btn1_Click(object sender, EventArgs e)

{

Estrategia Proceso = new Estrategia(new contexto(new NombreApellido()));

txt1.Text = Proceso.ProcesarNombres(txtNombre.Text, txtApellido.Text);

}



private void button1_Click(object sender, EventArgs e)

{

Estrategia Proceso = new Estrategia(new contexto(new Puente()));

txt2.Text = Proceso.ProcesarNombres(txtNombre.Text, txtApellido.Text);

}



private void btn3_Click(object sender, EventArgs e)

{

Estrategia Proceso = new Estrategia(new contexto(new Enriquecido()));

txt3.Text = Proceso.ProcesarNombres(txtNombre.Text, txtApellido.Text);

}



}

}
 
Como se puede observar es muy sencillo cambiar de estrategia y obtener un resultado distinto cada vez.

lunes, 4 de junio de 2007

El Patrón Estrategia

La estrategia describe una solución al problema principal inherente a escribir el código principal – como accionar con demandas inesperadas para cambios de implementación.
La definición formal de estrategia, fue dada por el "GoF" es:


"Define una familia de algoritmos, encapsula cada uno, y los hace intercambiables. La estrategia permite al algoritmo variar en dependencia del cliente que la utilice."



Esto resulta algo oscuro en una primera lectura, vamos a tratar de aclarar.


¿Cuáles son los componentes de una estrategia?


Una estrategia tiene tres componentes esenciales: "la estrategia abstracta", que es una clase que define la interfaz, y funcionalidad genérica para las "estrategias concretas", que son subclases que definen varias implementaciones posibles. El tercer componente, el "Contexto", es responsable de controlar la referencia a la implementación actual.






Si está pensando que este diagrama se ve muy similar al puente, está en lo cierto. Puede pensar incluso, que el patrón de estrategia es como un "puente dinámico" en el cual un final (el contexto) es estático; pero el otro (la estrategia) está creado por varias variantes con el propósito de entregar una implementación específica en un momento específico. La esencia del patrón es tal que la decisión como establecer qué subclase debe ser instanciada en cualquier momento dependa del estado del "cliente". El ejemplo clásico del patrón descansa en la responsabilidad de instanciar la estrategia concreta del objeto en el cliente, con la que pasa la referencia al contexto.

viernes, 1 de junio de 2007

Implementando el Patrón Decorador con Visual Fox Pro

Siguiendo la misma línea del artículo anterior, vamos a implementar el patrón decorador en Visual Fox Pro.





Dentro de un prg para mi caso ejemplos.prg vamos a escrbir las clases:






DEFINE CLASS IComponente as Custom
FUNCTION procesarNombres()
LPARAMETERS nombre,apellido
endfunc
ENDDEFINE



DEFINE CLASS puente as Icomponente

Caracter= ", "

PROCEDURE Caracter_ACCESS
RETURN THIS.Caracter
ENDPROC

PROCEDURE Caracter_ASSIGN
LPARAMETERS Valor
THIS.Caracter=Valor
ENDPROC

FUNCTION procesarNombres()
LPARAMETERS nombre,apellido
RETURN ALLTRIM(Apellido) + this.Caracter + ALLTRIM(nombre)
ENDFUNC

ENDDEFINE



DEFINE CLASS decorador as Icomponente

componente=null

PROCEDURE init ()
LPARAMETERS clase
this.componente=clase
ENDPROC

FUNCTION procesarNombres()
LPARAMETERS nombre,apellido
RETURN this.componente.procesarNombres(nombre,apellido)
ENDFUNC

ENDDEFINE


DEFINE CLASS AgregarNumeracion as decorador

padre=null

Numero= 1

PROCEDURE Numero_ACCESS
RETURN THIS.Numero
ENDPROC

PROCEDURE Numero_ASSIGN
LPARAMETERS Valor
THIS.Numero=Valor
ENDPROC

Separador= " - "

PROCEDURE Separador_ACCESS
RETURN THIS.Separador
ENDPROC

PROCEDURE Separador_ASSIGN
LPARAMETERS Valor
THIS.Separador=Valor
ENDPROC

PROCEDURE init ()
LPARAMETERS clase
this.padre=NEWOBJECT("decorador",'','',clase)
ENDPROC

FUNCTION procesarNombres()
LPARAMETERS nombre,apellido
RETURN ALLTRIM(STR(this.numero))+ this.separador + this.padre.procesarNombres(nombre,apellido)
ENDFUNC

ENDDEFINE

DEFINE CLASS AgregarNacionalidad as decorador

padre=null

Nacionalidad= "Argentina"

PROCEDURE Nacionalidad_ACCESS
RETURN THIS.Nacionalidad
ENDPROC

PROCEDURE Nacionalidad_ASSIGN
LPARAMETERS Valor
THIS.Nacionalidad=Valor
ENDPROC

Separador= " - "

PROCEDURE Separador_ACCESS
RETURN THIS.Separador
ENDPROC

PROCEDURE Separador_ASSIGN
LPARAMETERS Valor
THIS.Separador=Valor
ENDPROC


PROCEDURE init ()
LPARAMETERS clase
this.padre=NEWOBJECT("decorador",'','',clase)
ENDPROC

FUNCTION procesarNombres()
LPARAMETERS nombre,apellido
RETURN this.padre.procesarNombres(nombre,apellido) + this.separador + this.nacionalidad
ENDFUNC

ENDDEFINE






Ya estamos listos para consumir las clases. Creamos el mismo formulario que para C#:



En el Init del formulario hacemos una referencia al archivo de las clases...


SET PROCEDURE TO "ejemplos.prg"

En el primer botón colocamos...


MiPuente = NEWOBJECT("puente")
ThisForm.txt1.Value=MiPuente.procesarnombres(thisform.txtNombre.Value,thisform.txtApellido.Value)
RELEASE MiPuente

En el segundo...


MiPuente = NEWOBJECT("puente")
*agrego los decoradores
deco1 = NEWOBJECT("AgregarNumeracion",'','',MiPuente)
*llamo al método
ThisForm.txt2.Value=deco1.procesarnombres(thisform.txtNombre.Value,thisform.txtApellido.Value)
*elimino los objetos
RELEASE MiPuente
RELEASE deco1

En el tercero...


MiPuente = NEWOBJECT("puente")
*agrego los decoradores
deco2 = NEWOBJECT("AgregarNacionalidad",'','',MiPuente)
deco2.nacionalidad=thisform.txtNacionalidad.value
*llamo al método
ThisForm.txt3.Value=deco2.procesarnombres(thisform.txtNombre.Value,thisform.txtApellido.Value)
*elimino los objetos
RELEASE MiPuente
RELEASE deco2

En el cuarto...


MiPuente = NEWOBJECT("puente")
*agrego los decoradores
deco1 = NEWOBJECT("AgregarNumeracion",'','',MiPuente)
deco2 = NEWOBJECT("AgregarNacionalidad",'','',deco1)
deco2.nacionalidad=thisform.txtNacionalidad.value

*llamo al método
ThisForm.txt4.Value=deco2.procesarnombres(thisform.txtNombre.Value,thisform.txtApellido.Value)
*elimino los objetos
RELEASE MiPuente
RELEASE deco1
RELEASE deco2

y por último en el quinto...


*agrego los decoradores
deco2 = NEWOBJECT("AgregarNacionalidad",'','',NEWOBJECT("AgregarNumeracion",'','',NEWOBJECT("puente")))
deco2.nacionalidad=thisform.txtNacionalidad.value
*llamo al método
ThisForm.txt5.Value=deco2.procesarnombres(thisform.txtNombre.Value,thisform.txtApellido.Value)
*elimino los objetos
RELEASE deco2

Como se puede ver, al igual que en artículo anterior, con una sola línea se puede agregar funcionalidad de forma dinámica al objeto.


deco2 = NEWOBJECT("AgregarNacionalidad",'','',NEWOBJECT("AgregarNumeracion",'','',NEWOBJECT("puente")))

El patrón decorador es muy útil en ambientes multiempresa, por ejemplo, donde las reglas de negocio son muy cambiantes entre sí, e incluso entre sucursales. Con este patrón se podría manejar de forma más eficiente la diversidad de funcionalidades que el trabajo multiempresa significa.


Implementando el Patrón Decorador con C#

Para empezar vamos a Implementar el patrón decorador en C#. Lo primero será crear la interfaz IProcesarNombres.





using System;
using System.Collections.Generic;
using System.Text;
namespace patronDecorador
{
public interface IProcesarNombres
{
String ProcesarNombres(String Nombre, String Apellido);
}
}






Ahora bien, nuestra clase (en este caso puente) debe implementar esta interfaz.





using System;
using System.Collections.Generic;
using System.Text;
namespace patronDecorador
{
public class Puente : IProcesarNombres
{
private String _caracter = ", ";
public String Caracter
{
get { return _caracter; }
set { _caracter = value.Trim(); }
}
public String ProcesarNombres(String Nombre, String Apellido)
{
return Apellido.Trim() + Caracter + Nombre.Trim();
}
}
}







El decorador también debe implementarla como se muestra a continuación.






using System;
using System.Collections.Generic;
using System.Text;
namespace patronDecorador
{
public abstract class Decorador : IProcesarNombres
{
protected IProcesarNombres _Componente;
public Decorador(IProcesarNombres Clase)
{
_Componente = Clase;
}
public virtual String ProcesarNombres(String Nombre, String Apellido)
{
return _Componente.ProcesarNombres(Nombre, Apellido);
}
}
}



Luego creamos dos clases decoradoras:





1) AgregarNumeracion:



using System;
using System.Collections.Generic;
using System.Text;
namespace patronDecorador
{
public class AgregarNumeracion : Decorador
{
private Int32 _numero = 1;
public Int32 Numero
{
get { return _numero; }
set { _numero = value; }
}

private String _separador = " - ";
public String Separador
{
get { return _separador; }
set { _separador = value; }
}

public AgregarNumeracion(IProcesarNombres Clase): base(Clase){}

public override String ProcesarNombres(String Nombre, String Apellido)
{
return Numero.ToString()+ Separador + base.ProcesarNombres(Nombre, Apellido);
}
}
}





2) AgregarNacionalidad :



using System;
using System.Collections.Generic;
using System.Text;
namespace patronDecorador
{
public class AgregarNacionalidad : Decorador
{
private String _Nacionalidad= "Argentina";
public String Nacionalidad
{
get { return _Nacionalidad; }
set { _Nacionalidad = value.Trim(); }
}

private String _separador = " - ";
public String Separador
{
get { return _separador; }
set { _separador = value; }
}

public AgregarNacionalidad(IProcesarNombres Clase): base(Clase){}

public override String ProcesarNombres(String Nombre, String Apellido)
{
return base.ProcesarNombres(Nombre, Apellido) + Separador +Nacionalidad;
}
}
}








Ahora ya se puede comenzar a usarlas. Creamos el siguiente formulario:



A continuación el código que le da la funcionalidad:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using patronDecorador;
namespace WindowsApplication1
{
public partial class Decorador : Form
{
public Decorador()
{
InitializeComponent();
}

private void btn1_Click(object sender, EventArgs e)
{
Puente MiClase = new Puente();
txt1.Text= MiClase.ProcesarNombres(txtNombre.Text,txtApellido.Text);
}
private void btn2_Click(object sender, EventArgs e)
{
Puente MiClase = new Puente();
AgregarNumeracion Deco1 = new AgregarNumeracion(MiClase);
txt2.Text = Deco1.ProcesarNombres(txtNombre.Text,txtApellido.Text);
}
private void btn3_Click(object sender, EventArgs e)
{
Puente MiClase = new Puente();
AgregarNacionalidad Deco2 = new AgregarNacionalidad(MiClase);
Deco2.Nacionalidad = txtNacionalidad.Text;
txt3.Text = Deco2.ProcesarNombres(txtNombre.Text, txtApellido.Text);
}
private void btn4_Click(object sender, EventArgs e)
{
Puente MiClase = new Puente();
AgregarNumeracion Deco1 = new AgregarNumeracion(MiClase);
AgregarNacionalidad Deco2 = new AgregarNacionalidad(Deco1);
Deco2.Nacionalidad = txtNacionalidad.Text;
txt4.Text = Deco2.ProcesarNombres(txtNombre.Text, txtApellido.Text);
}
private void btn5_Click_1(object sender, EventArgs e)
{
AgregarNacionalidad FormaReducida = new AgregarNacionalidad(new AgregarNumeracion(new Puente()));
FormaReducida.Nacionalidad = txtNacionalidad.Text;
txt5.Text = FormaReducida.ProcesarNombres(txtNombre.Text, txtApellido.Text);
}
}
}

Como verán con una sola y sencilla linea se agrega funcionalidad al objeto:

AgregarNacionalidad FormaReducida = new AgregarNacionalidad(new AgregarNumeracion(new Puente()));

Esta es la principal ventaja del patrón Decorador.

A continuación su aplicación en VFP.


El Patrón Decorador

El decorador describe una solución al problema de agregar una funcionalidad a un objeto sin cambiar realmente el código en el objeto.
La definición formal del decorador, dada por el "GoF" es:

"Adjuntar responsabilidad adicional a un objeto dinámicamente."

La necesidad de cambiar dinámicamente la funcionalidad o comportamiento de un objeto surge típicamente en una de estas dos situaciones. Primero, cuando el código fuente del objeto está no disponible, puede ser que el objeto es un control ActiveX o una biblioteca de clases de terceras partes está utilizada. Segundo, cuando la clase que es ampliamente utilizada para la responsabilidad específica, se necesita sólo en una o más situaciones

La principal diferencia entre usar subclases y el patrón Decorador es que con las subclases se trabaja directamente con la clase, en cambio con el patrón Decorador se modifican los objetos dinámicamente. Cuando se extiende una clase, los cambios hechos a las clases hijos serán afectados a todas las instancias de las clases hijos; sin embargo, con el patrón Decorador se aplican los cambios a cada objeto individual que se quiere cambiar.

Estructura del patrón Decorador





A continuación describiremos la estructura:

Componente: Clase abstracta común a todas las clases susceptibles de ser ampliadas con el Decorador.

ComponenteConcreto: Son las clases cuya funcionalidad se puede extender y en las que se delega en última instancia para realizar las operaciones propias de la clase.

Decorator: Clase abstracta que declara la estructura común a todos los Decoradores y declara (o implementa, según sea el caso) la responsabilidad de mantener una referencia al objeto que se extiende. Es posible que sobrecargue todos los métodos de la clase Componente con llamadas al componente concreto, de forma que aquellos métodos cuya funcionalidad no se extiende simplemente llaman a los originales.

DecoradorConcreto1 y DecoradorConcreto2: Son clases concretas que heredan de Decorator e implementan las extensiones de funcionalidad de la clase original ComponenteConcreto.


Este patrón es en realidad un puente extendido. Debido a que el cliente está afectado, puede dirigir el objeto decorador como si realmente fuera la implementación al final del puente estándar, porque la interfaz de los dos es la misma. Debido a que la implementación está afectada, la petición mira exactamente igual que si fuera directamente desde el cliente. No necesita nunca conocer que incluso que el decorador existe.
En los próximos dos artículos veremos como se implementa este patrón en VFP y C# extendiendo el ejemplo del patrón puente que venimos tratando en artículos anteriores.

García Marquez en Macondo


Anoche estuve viendo en la tele que Gabriel García Márquez regreso a su tierra natal, el pueblo que inspiro su tan famoso Macondo. Como verán uno de los libros que recomiendo es “Cien años de Soledad”. Este libro es una joya de la literatura. Tal vez hay algunos que dicen que es un libro difícil de leer y de mantener el hilo conductivo, pero yo creo que en realidad es una obra magnifica. Tiene un dinamismo sensacional. No es una historia más contada en un libro muy bien escrito, todo lo contrario, es una recopilación de historias entrelazadas de forma coherente y con gusto más que superior.
Es por ello que recomiendo este libro que yo definiría como “Delicioso”.