Software y Aplicaciones Web

Blog de desarrollo de software y aplicaciones web

Comentarios Recientes

Comment RSS

MSDN Home Page (Argentina)


C# Corner


AspAlliance.com

Declaración

Las opiniones en este blog se proporcionan "TAL CUAL", sin garantías,  no confieren derechos y no reflejan, necesariamente, la opinión de quienes me contratan.
Algunas cuestiones que se comentan en el blog no son reales, cualquier similitud con alguna persona viva o muerta no es más que una coincidencia, tampoco significa que necesite terapia, soy asi.

© Copyright 2007-2010

Propaganda

Este sitio implementa publicidad basada en intereses
Aug
25.
2009

  Mi Primer Objeto - parte 5

Continuamos con el ejercicio que nos permite aprender un poco más de los conceptos de la programación orientada a objetos. Nuestro caballito de batalla, el objeto del tipo Bolsa es útil para ello (obviamente ya se comentó que en la BCL- Base Class Libraries) existen clases que implementan el comportamiento de una bolsa, pero el objetivo es justamente comenzar desde cero y analizar todos los aspectos posibles.

Ya se realizó una derivación de la Bolsa que nos permite tener dentro de la bolsa solamente un elemento por cada posible valor, prácticamente un Conjunto (salvo que no se pueden realizar las operaciones de la matemática: Unión, Intersección, etc.)

Veamos si es posible tener una bolsa ordenada, esto sería que los elementos se agregan en la bolsa pero se mantiene ordenados dentro de ella. La solución obvia es derivar (declarar una clase hija de la clase Bolsa, que implemente este comportamiento específico).

 

El código podría ser el siguiente:

   1:  using System;
   2:   
   3:  namespace DemoObjeto5
   4:  {
   5:    /// <summary>
   6:    /// Declaración de la clase OrderdBag para implementar el
   7:    /// comportamiento especial de una Bolsa en la que se desea
   8:    /// que los elementos se encuentren ordenados
   9:    /// </summary>
  10:    /// <typeparam name="ELEMENTO">Tipo del elemento que se almacena</typeparam>
  11:    public class OrderedBag<ELEMENTO> : Bolsa<ELEMENTO>
  12:    {
  13:      public new  OrderedBag<ELEMENTO> Agregar(ELEMENTO x)
  14:      {
  15:        base.Agregar(x);
  16:        Array.Sort<ELEMENTO>(datos);
  17:        return this;
  18:      }
  19:    }
  20:  }

Como se observa, la clase OrderedBag es subclase de Bolsa y por supuesto ambas utilizan programación genérica para poder crear objetos que contenga lo que nos haga falta.

La primer idea es agregar el nuevo elemento utilizando el método de la clase base y luego ordenar el arreglo (ya que los arreglos facilitan esa posibilidad), para ello hace falta indicar en la clase base (Bolsa) que el atributo "datos" tenga visibilidad "protected" caso contrario desde OrderedBag se hereda el atributo pero no se lo puede ver.

Compilamos, todo anda bien y probamos el siguiente código:

   1:      static void Main(string[] args)
   2:      {
   3:        OrderedBag<int> ob1 = new OrderedBag<int>();
   4:        ob1.Agregar(15);
   5:        ob1.Agregar(25);
   6:        ob1.Agregar(18);
   7:        Console.WriteLine("Bolsa Ordenada : {0}", ob1.ToString());
   8:      }

La salida es:

Ups, donde están los números.

Si averiguamos un poco, vamos a descubrir que el método Sort de los objetos tipo Array ordena de menor a mayor (al menos como se lo está usando) y resulta que los últimos elementos del arreglo tienen el valor por defecto (que es cero), seguramente nuestro números están al final del arreglo que está en la estructura interna de la bolsa.

Esto es consecuencia de la pobrísima implementación que tenemos como estructura interna, en este punto debemos considerar cambiar la estructura interna por "algo" que sea mejor o seguir inventando la rueda (volver a escribir código que ya está escrito). Solamente para probar se nos podría ocurrir lo siguiente:

   1:      public new  OrderedBag<ELEMENTO> Agregar(ELEMENTO x)
   2:      {
   3:        if (cantidad == datos.Length)
   4:        {
   5:          // Agranda el arreglo de la estructura interna
   6:          Array.Resize<ELEMENTO>(ref datos, datos.Length + 50);
   7:        }
   8:        // Busca el lugar en el arreglo donde se debe insertar el valor x
   9:        int i = 0;
  10:        for (; i < cantidad; ++i)
  11:        {
  12:          if (datos[i] > x)
  13:            break;
  14:        }
  15:        // Mueve los elementos para hacer un lugar para x
  16:        for (int j = cantidad; j > i; --j)
  17:        {
  18:          datos[j] = datos[j - 1];
  19:        }
  20:        datos[i] = x;
  21:        ++cantidad;
  22:        return this;
  23:      }

En el método Agregar de OrderedBag decidimos "insertar en orden"; verificamos que el tamaño del arreglo sea el adecuado, luego buscamos el lugar donde debe estar el nuevo elemento, nos hacemos lugar y finalmente lo asignamos.

Como ejemplo de programación sirve, pero no va a funcionar. Si tratamos de compilar este código vamos a descubrir que los objetos del tipo ELEMENTO no saben cómo resolver la comparación " > " que está en la línea 12. Por supuesto, al tratarse de programación genérica no se puede asumir que ELEMENTO es un entero o un caracter, eso se define al momento de crear el objeto y en consecuencia el compilador no puede generar el código adecuado porque "no se sabe cómo comparar objetos del tipo ELEMENTO".

Ya que estamos en el baile, bailemos . Rebuscando en las posibilidad que brinda la programación orientada a objetos y descubrimos que sería posible enseñarle a cada objeto del tipo ELEMENTO a compararse, esto es agreguemos un método que nos diga cuando un objeto es mayor que otro.

Llegó el momento de parar la pelota, ya que no tiene sentido continuar programando cosas que están hechas y probadas; de manera que vamos a cambiar la estructura interna de la bolsa utilizando un ArrayList que además se puede ordenar considerando solamente los elementos que en el ArrayList están.

Tomamos la declaración de la clase Bolsa y realizamos todos los cambios necesarios para reemplazar el atributo "datos" que inicialmente es un arreglo de ELEMENTO por un ArrayList; dado que los objetos del tipo ArrayList publican una propiedad "Count" que indica cuantos elementos hay en el ArrayList ya no hace falta el atributo "cantidad" de manera que lo suprimimos y por supuesto corregimos el código para que utilice esta propiedad.

El código de Bolsa.cs (mejorado) queda así:

   1:  using System;
   2:  using System.Collections;
   3:  using System.Collections.Generic;
   4:   
   5:  namespace DemoObjeto5
   6:  {
   7:    /// <summary>
   8:    /// Definición para aquellos objetos que presentan el comportamiento 
   9:    /// asociado con una "bolsa" que contine "elementos", en la bolsa se
  10:    /// puede agregar o sacar elementos, tambiéne es posible averiguar si
  11:    /// está vacía, o cuantos elementos tiene en total así como cuantos
  12:    /// elementos de algun valor hay.
  13:    /// 
  14:    /// Implementa la bolsa para objetos de cualquier tipo !!!
  15:    /// 
  16:    /// </summary>
  17:    public class Bolsa <ELEMENTO>  
  18:    {
  19:   
  20:      #region Campos de la Estructura Interna de cada objeto
  21:   
  22:      /// <summary>
  23:      /// sirve para almacenar o guardar los elementos dentro del objeto
  24:      /// inicialmente tiene una capacidad para 100 caracteres.
  25:      /// </summary>
  26:      protected ArrayList datos;
  27:   
  28:      #endregion
  29:   
  30:      #region Constructores
  31:   
  32:      /// <summary>
  33:      /// Constructor por defecto, incializa los elementos de la estructura
  34:      /// interna del objeto.
  35:      /// </summary>
  36:      public Bolsa()
  37:      {
  38:        datos = new ArrayList(100);
  39:      }
  40:   
  41:      /// <summary>
  42:      /// Constructor especializado, permite indicar el tamaño o dimensión
  43:      /// del contenedor (arreglo) para los elementos de la Bolsa.
  44:      /// </summary>
  45:      /// <exception cref="ArgumentException">
  46:      /// Dispara la excepción de argumento inválido cuando el parametro es cero o menor
  47:      /// </exception>
  48:      /// <param name="dimension">Tamaño incial del contenedor (debe ser mayor que cero)</param>
  49:      public Bolsa(int dimension)
  50:      {
  51:        if (dimension > 0)
  52:        {
  53:          datos = new ArrayList(dimension);
  54:        }
  55:        else
  56:        {
  57:          throw new ArgumentException("Argumento incorrecto, dimension = " + dimension.ToString());
  58:        }
  59:      }
  60:   
  61:      /// <summary>
  62:      /// Constructor especializado, permite crear un objeto como copia de otra
  63:      /// </summary>
  64:      /// <exception cref="ArgumentException">
  65:      /// Dispara la excepción de argumento inválido cuando el parametro es nulo o
  66:      /// no es del tipo Bolsa
  67:      /// </exception>
  68:      /// <param name="origen">objeto del tipo Bolsa que se copia</param>
  69:      public Bolsa(Bolsa<ELEMENTO> origen)
  70:      {
  71:        if ((origen != null) && (origen is Bolsa<ELEMENTO>))
  72:        {
  73:          datos = new ArrayList(origen.Contar());
  74:   
  75:          for (int i = 0; i < origen.Contar(); ++i)
  76:          {
  77:            // ESTO SOLAMENTE COPIA LA REFERENCIA AL OBJETO 
  78:            datos[i] = origen.datos[i];
  79:          }
  80:        }
  81:        else
  82:        {
  83:          throw new ArgumentException("Argumento incorrecto, origen = null");
  84:        }
  85:      }
  86:   
  87:      #endregion
  88:   
  89:   
  90:      #region Métodos públicos, implementan los mensajes
  91:   
  92:      /// <summary>
  93:      /// Agrega un elemento al objeto del tipo bolsa
  94:      /// </summary>
  95:      /// <param name="x">Elemento que se desea agregar</param>
  96:      /// <returns>Referencia al mismo objeto bolsa que ahora continene el nuevo elemento</returns>
  97:      public virtual Bolsa<ELEMENTO> Agregar(ELEMENTO x)
  98:      {
  99:        datos.Add(x);
 100:        return this;
 101:      }
 102:   
 103:      /// <summary>
 104:      /// Cuenta cuántos elementos hay en un objeto del tipo bolsa
 105:      /// </summary>
 106:      /// <returns>Cantidad de elementos que hay en el objeto bolsa</returns>
 107:      public int Contar()
 108:      {
 109:        return datos.Count;
 110:      }
 111:   
 112:      /// <summary>
 113:      /// Cuenta cuántos elemetnos de un determinado valor se encuentran dentro
 114:      /// de un objeto del tipo bolsa
 115:      /// </summary>
 116:      /// <param name="x">Valor que se desea averiguar</param>
 117:      /// <returns>Cantidad de elementos del valor dado que hay en el objeto bolsa</returns>
 118:      public int Contar(ELEMENTO x)
 119:      {
 120:        int cuenta = 0;
 121:        for (int i = 0; i < datos.Count; ++i)
 122:        {
 123:          // Es necesario utilizar el mensaje Equals
 124:          if (datos[i].Equals(x))
 125:          {
 126:            ++cuenta;
 127:          }
 128:        }
 129:        return cuenta;
 130:      }
 131:   
 132:      /// <summary>
 133:      /// Averigua si un elemento está o no dentro de un objeto del tipo bolsa
 134:      /// </summary>
 135:      /// <param name="x">Elemento que se desea controlar</param>
 136:      /// <returns>Verdadero si el elemento está en la bolsa, caso contrario falso</returns>
 137:      public bool Existe(ELEMENTO x)
 138:      {
 139:        return datos.Contains(x);
 140:      }
 141:   
 142:      /// <summary>
 143:      /// Saca o retira el elemento indicado del objeto del tipo bolsa, 
 144:      /// </summary>
 145:      /// <param name="x">Elemento a retirar o sacar</param>
 146:      /// <returns>Referencia al miso objeto bolsa sin el elemento indicado</returns>
 147:      public Bolsa<ELEMENTO> Sacar(ELEMENTO x)
 148:      {
 149:        if (Existe(x))
 150:        {
 151:          datos.Remove(x);
 152:        }
 153:        return this;
 154:      }
 155:   
 156:      /// <summary>
 157:      /// Genera una cadena con todos los elementos que hay en el objeto
 158:      /// del tipo bolsa.
 159:      /// </summary>
 160:      /// <remarks>
 161:      /// ESTA NO ES LA MEJOR IMPLEMENTACIÓN, PERO SIRVE PARA EL EJEMPLO 
 162:      /// DEBERÍA UTILIZARSE UN OBJETO StringBuilder (luego lo veremos)
 163:      /// </remarks>
 164:      /// <returns>Cadena con la representación de cada elemento que hay en el objeto bolsa</returns>
 165:      public override string ToString()
 166:      {
 167:        string s = "";
 168:        for (int i = 0; i < datos.Count; ++i)
 169:        {
 170:          s = s + "\n\t" + datos[i].ToString() + " ";
 171:        }
 172:        return s;
 173:      }
 174:   
 175:   
 176:      /// <summary>
 177:      /// Determina si el objeto (Bolsa) es igual al objeto que se pasa
 178:      /// como argumento. La única posibilidad es que el argumento también
 179:      /// sea del tipo Bolsa.
 180:      /// </summary>
 181:      /// <param name="obj">Objeto con el que se compara</param>
 182:      /// <returns>verdadero si ambos objetos son iguales</returns>
 183:      public override bool Equals(object obj)
 184:      {
 185:        if ((obj != null) && (obj is Bolsa<ELEMENTO>))
 186:        {
 187:          return Equals((Bolsa<ELEMENTO>)obj);
 188:        }
 189:        return false;
 190:      }
 191:   
 192:      /// <summary>
 193:      /// Generá un número, mediante técnicas hash que se corresponde con el 
 194:      /// contenido interno del objeto.
 195:      /// </summary>
 196:      /// <returns>Valor del código hash</returns>
 197:      public override int GetHashCode()
 198:      {
 199:        return base.GetHashCode();
 200:      }
 201:   
 202:      /// <summary>
 203:      /// Determina si el objeto del tipo Bolsa es igual a la bolsa que se
 204:      /// pasa como argumento.
 205:      /// Todos los elementos de una bolsa deben existir en la otra bolsa,
 206:      /// el control debe realizarse en ambas para superar el siguiente caso
 207:      ///   bolsa1 = ['A', 'B', 'B']
 208:      ///   bolsa2 = ['A', 'B', 'C']
 209:      /// 
 210:      /// </summary>
 211:      /// <param name="otra">Objeto del tipo bolsa con el que se compara</param>
 212:      /// <returns>Verdadero si ambas bolsas son iguales</returns>
 213:      public bool Equals(Bolsa<ELEMENTO> otra)
 214:      {
 215:        if ((otra != null) && (this.datos.Count == otra.datos.Count))
 216:        {
 217:          for (int i = 0; i < this.datos.Count; ++i) // este ciclo controla el objeto emisor
 218:          {
 219:            if (!otra.Existe((ELEMENTO)this.datos[i]))
 220:            {
 221:              return false;
 222:            }
 223:          }
 224:          for (int i = 0; i < otra.datos.Count; ++i) // este ciclo controla el objeto parámetro
 225:          {
 226:            if (!this.Existe((ELEMENTO)otra.datos[i]))
 227:            {
 228:              return false;
 229:            }
 230:          }
 231:          return true;
 232:        }
 233:        return false;
 234:      }
 235:   
 236:      #endregion
 237:   
 238:      #region Iteradores (a mano)
 239:   
 240:      /// <summary>
 241:      /// Implementación del método que obtiene un enumerador del objeto
 242:      /// Esto se utiliza internamente en las sentencias foreach(...)
 243:      /// </summary>
 244:      /// <returns>Referencia al código que implementa el enumerador</returns>
 245:      public IEnumerator<ELEMENTO> GetEnumerator()
 246:      {
 247:        return MiEnumerador();
 248:      }
 249:   
 250:      /// <summary>
 251:      /// Implementación propia de un enumerado para permitir el uso de la
 252:      /// sentencia foreach(...) con objetos del tipo Bolsa
 253:      /// Utiliza la sentencia yield para entregar los elementos de uno
 254:      /// </summary>
 255:      /// <returns>Cada elemento de la bolsa de uno de por vez</returns>
 256:      private IEnumerator<ELEMENTO> MiEnumerador()
 257:      {
 258:        for (int i = 0; i < datos.Count; ++i)
 259:        {
 260:          yield return (ELEMENTO)datos[i];
 261:        }
 262:      }
 263:   
 264:      #endregion
 265:   
 266:    }
 267:  }

Después de probar que el comportamiento que hacía antes se mantiene podemos probar si es posible mantener una bolsa ordenada. (la prueba del comportamiento anterior se las dejo por su cuenta).

Ahora en OrderedBag.cs ponemos lo siguiente:

   1:  using System;
   2:   
   3:  namespace DemoObjeto5
   4:  {
   5:    /// <summary>
   6:    /// Declaración de la clase OrderdBag para implementar el
   7:    /// comportamiento especial de una Bolsa en la que se desea
   8:    /// que los elementos se encuentren ordenados
   9:    /// </summary>
  10:    /// <typeparam name="ELEMENTO">Tipo del elemento que se almacena</typeparam>
  11:    public class OrderedBag<ELEMENTO> : Bolsa<ELEMENTO>
  12:    {
  13:      public new  OrderedBag<ELEMENTO> Agregar(ELEMENTO x)
  14:      {
  15:        base.Agregar(x);
  16:        datos.Sort();
  17:        return this;
  18:      }
  19:    }
  20:  }

Para ver si funciona hacemos lo siguiente en Program.cs

   1:      static void Main(string[] args)
   2:      {
   3:        OrderedBag<int> ob1 = new OrderedBag<int>();
   4:        ob1.Agregar(15);
   5:        ob1.Agregar(25);
   6:        ob1.Agregar(18);
   7:        Console.WriteLine("Bolsa Ordenada : {0}", ob1.ToString());
   8:        ob1.Agregar(15);
   9:        ob1.Agregar(25);
  10:        ob1.Agregar(18);
  11:        Console.WriteLine("Bolsa Ordenada : {0}", ob1.ToString());
  12:        ob1.Sacar(18);
  13:        Console.WriteLine("Bolsa Ordenada : {0}", ob1.ToString());
  14:      }

La salida es:

Objetivo cumplido !!!

Como nuestra bolsa puede contener cualquier tipo de objeto, veamos si se puede ordenar objetos del tipo Persona, probamos el siguiente código:

   1:        OrderedBag<Persona> ob2 = new OrderedBag<Persona>();
   2:        ob2.Agregar(new Persona(12345678, "Perez", "Juan"));
   3:        ob2.Agregar(new Persona(87654321, "Ramirez", "Alberto"));
   4:        ob2.Agregar(new Persona(55555555, "Alvarez", "Roberto"));
   5:        Console.WriteLine("Bolsa Ordenada : {0}", ob2.ToString());

Compilamos sin errores y a la hora de ejecutar CHAN ... se muere y después de un rato nos dice que no sabe comparar personas. Claro nunca implementamos ese comportamiento, como se hace ???

Como digo en mis clases, llego el momento de aprender algo más. Se trata de la famosa "interface" que no es otra cosa que un "Contrato de comportamiento", es con las interfaces que se puede "obligar" a una clase a realizar un comportamiento en particular. En este caso necesitamos que los objetos del tipo Persona se puedan comparar unos con otros. En C# existe una interface que se puede utilizar, es la denominada "IComparable"

Para que una clase se vea obligada a implementar un comportamiento en particular, se debe indicar en la declaración de la clase que implementa una interface; eso se hace así:

   1:    public class Persona : IComparable
   2:    {

De este modo es imposible compilar el código si no se implementa todo lo que IComparable indica. Para ello en la clase Persona se debe agregar lo siguiente:

   1:      /// <summary>
   2:      /// Compara el objeto que recibe el mensaje con otro objeto (argumento)
   3:      /// Indudablemente ese otro objeto debe ser del mismo tipo para poder
   4:      /// realizar la comparación que en este caso considera el DNI
   5:      /// </summary>
   6:      /// <param name="obj">Objeto a compara</param>
   7:      /// <returns>Valor negativo si este objeto es menor, cero si son iguales y positivo si este objeto es mayor</returns>
   8:      int IComparable.CompareTo(object obj)
   9:      {
  10:        if ((obj != null) && (obj is Persona))
  11:        {
  12:          if (this.DNI < ((Persona)obj).DNI)
  13:            return -1;
  14:          else
  15:            if (this.DNI == ((Persona)obj).DNI)
  16:              return 0;
  17:            else
  18:              return 1;
  19:        }
  20:        else
  21:        {
  22:          throw new ArgumentException("Argumento inválido obj");
  23:        }
  24:      }

Obviamente este código se puede resolver con una simple resta entre this.DNI y ((Persona)obj).DNI, pero de esta forma queda más claro para los novatosWink

Ahora se puede ejecutar el código donde tratamos de mantener una bolsa ordenada de personas, la salida será:

Perfecto, tenemos una bolsa ordenada de objetos del tipo Persona.

El único problema que todavía debemos resolver es cuando tratemos de mantener una bolsa ordenada de objetos que no implementan la interface IComparable; cuando compilamos todo va bien pero a la hora de ejecutar CRASH...

Esto se puede solucionar indicando en la declaración de OrderedBag que ELEMENTO debe implementar IComparable, de ese modo cuando se trata de compilar para objetos que no lo hacen es el compilador quién nos indica que estamos cometiendo un error. Eso se hace así:

   1:    /// <summary>
   2:    /// Declaración de la clase OrderdBag para implementar el
   3:    /// comportamiento especial de una Bolsa en la que se desea
   4:    /// que los elementos se encuentren ordenados
   5:    /// </summary>
   6:    /// <typeparam name="ELEMENTO">Tipo del elemento que se almacena</typeparam>
   7:    public class OrderedBag<ELEMENTO> : Bolsa<ELEMENTO> where ELEMENTO : IComparable
   8:    {
   9:      public new  OrderedBag<ELEMENTO> Agregar(ELEMENTO x)
  10:      {
  11:        base.Agregar(x);
  12:        datos.Sort();
  13:        return this;
  14:      }
  15:    }

Observen la línea 7, con where ELEMENTO: IComparable se le indica al compilador que ELEMENTO debe implementar la interface IComparable si no lo hace presenta error.

 

Conceptos a rescatar:

  1. Cambiamos la estructura interna de la clase Bolsa y el "mundo exterior" no sufrió. Esto es una de las fortalezas que la POO brinda.
  2. Los objetos del tipo Bolsa contienen objetos de cualquier tipo, y cada uno de estos objetos "sabe" cómo compararse; la bolsa desconoce que tipo de comparación realizan. Esto es otra fortaleza de la POO.

Espero que sirva.

Para leer fuera de línea: Mi Primer Objeto - parte 5.pdf (132,98 kb)








Add comment




  Country flag
biuquote
  • Comment
  • Preview
Loading