Académique Documents
Professionnel Documents
Culture Documents
Vamos a continuar con la realizacin del objeto Bolsa que planteamos en la primer parte de esta publicacin. En este caso vamos a trabajar sobre la comparacin de objetos, el concepto de igual y el concepto de idntico. Tambin veremos algo sobre constructores y finalmente implementaremos la clase Bolsa para objetos.
En la teora de programacin orientada a objetos aprendemos que los objetos tienen "identidad", lo que significa que cada objeto (como las personas) son nicos, pueden contener los mismos datos lo que significa que son "iguales" pero nunca sern "idnticos". La mejor forma de visualizar esta situacin es con el siguiente cdigo:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: static void Main(string[] args) { int[] v1 = new int[] { 1, 2, 3 }; int[] v2 = new int[] { 1, 2, 3 }; Console.Write("\nv1 : "); for (int i = 0; i < v1.Length; ++i) { Console.Write("{0} ", v1[i]); } Console.Write("\nv2 : "); for (int i = 0; i < v2.Length; ++i) { Console.Write("{0} ", v2[i]); } Console.Write("\nEl mtodo Equals devuelve "); if (v1.Equals(v2)) { Console.WriteLine("IGUALES"); } else { Console.WriteLine("DISTINTOS"); } }
Como pueden ver se declara dos "arreglos" de enteros y se inicializan con los mismos valores; a continuacin se muestra el contenido de cada arreglo y luego se invoca el mtodo "Equals" que tienen estos objetos. El mtodo "Equals" est implementado en la primer clase de la jerarqua de clases del lenguaje, es un mtodo de la clase "objetc", absolutamente todas las implementaciones que ya vienen con el Framework o que nosotros hagamos heredan ese mtodo. A continuacin se muestra la salida de este programa:
Dice que los vectores son DISTINTOS !!! Lo cual es correcto, porque cada uno de esos vectores hace referencia a dos zonas diferentes en el Heap, recordemos que cada objeto tiene un rea de memoria en el Stack donde se guarda la referencia y otra rea de memoria en el Heap donde se guarda el objeto mismo, esto se explica en Estructura de Datos y Programacin Orientada a Objetos. Consecuentemente cuando se invoca el mtodo Equals de un objeto lo que se compara es la direccin del rea de memoria del Heap a la cual est haciendo referencia. Esto se puede ver mejor en el siguiente esquema:
Lo importante es que a todos los objetos se les puede invocar el mensaje Equals para averiguar si ul objeto en cuestin es o no igual a otro objeto, el problema es que si nosotros no escribimos el cdigo entonces el resultado siempre ser DISTINTO (falso). Lo que debemos hacer es sobre escribir el mtodo que se hereda de la clase "object" y implementar nuestra propia versin de la comparacin, aquel cdigo que compare lo que nosotros necesitamos que se compare para determinar la igualdad. Es importante tomarse un momento para pensar esta situacin porque las especificaciones que nosotros escribamos seguramente se utilizarn en otras implementaciones que pueden necesitar de este comportamiento. El siguiente cdigo implemente nuestro mtodo Equals
1: 2: 3: 4: 5: /// /// /// /// /// <summary> Determina si el objeto del tipo Bolsa es igual a la bolsa que se pasa como argumento. Todos los elementos de una bolsa deben existir en la otra bolsa, el control debe realizarse en ambas para superar el siguiente caso
bolsa con el que se compara</param> son iguales</returns> == otra.cantidad)) ++i) // este ciclo controla el objeto
Este mtodo lo que hace es recibir como parmetro un objeto del tipo Bolsa, y si la cantidad de elementos de ambas el objeto emisor (el que ejecuta el mtodo) y el objeto parmetro son iguales procede a controlar que cada elemento de una bolsa exista en la otra. El punto es que todava no es modificado el mtodo que se hereda de la clase "object", eso es porque ese mensaje/mtodo tiene la siguiente firma "bool Equals(object obj)", recibe como parmetro un "object" no recibe una "Bolsa". Para evitar que futuros usos de nuestro objeto fracasen debemos sobre escribir el mtodo heredado.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: /// <summary> /// Determina si el objeto (Bolsa) es igual al objeto que se pasa /// como argumento. La nica posibilidad es que el argumento tambin /// sea del tipo Bolsa. /// </summary> /// <param name="obj">Objeto con el que se compara</param> /// <returns>verdadero si ambos objetos son iguales</returns> public override bool Equals(object obj) { if ((obj != null) && (obj is Bolsa)) { return Equals((Bolsa)obj); } return false; }
Con ste cdigo podemos estar seguros que se realizar la comparacin que queremos. Se debe destacar que hace falta la palabra reservada "override" para indicar que sobre escribimos el mtodo heredado; se controla que el parmetro no sea nulo y en caso de ser vlido que sea del tipo Bolsa para invocar nuestro mtodo Equals. Nota: Cuando escriban este cdigo o algo parecido el entorno de desarrollo del Visual Studio va a mostrar un mensaje de advertencia o warning que dice "Se est sobre escribiendo Equals pero no se sobre escribe GetHashCode". Esto es as porque en general para determinar si un objeto es igual a otro se suele utilizar tcnicas de hash para generar un nmero que surge de los valores internos del objeto. La advertencia o warning no es un error, de manera que la aplicacin puede funcionar. Pero es mejor evitar todas las advertencias entonces se puede agregar el siguiente mtodo que sobre escribe el que se hereda.
1: 2: 3: 4: 5: 6: 7: 8: 9: /// <summary> /// Gener un nmero, mediante tcnicas hash que se corresponde con el /// contenido interno del objeto. /// </summary> /// <returns>Valor del cdigo hash</returns> public override int GetHashCode() { return base.GetHashCode(); }
Este cdigo lo nico que hace es invocar el mismo mtodo pero de la clase base, por eso utiliza otra pseudovariable "base". Cuando realicemos ejercicios de jerarqua de clases veremos con ms detalle esta pseudovariable. Esto es solo para no tener la advertencia molestando a cada rato.
Algo sobre constructores: En la teora nos ensean que los objetos se crean gracias a un mtodo conocido como "constructor" que es el encargado de inicializar los elementos de la estructura interna de un objeto. En nuestro ejemplo de Bolsa nos hemos encargado de fijar los valores iniciales de cada elemento (el arreglo y el entero) de manera que no hace falta un constructor, sin embargo sabemos que aunque no lo implementemos este mtodo est disponible. Veamos cmo quedara nuestro ejemplo si codificamos el constructor por defecto (aquel que no tiene parmetros).
1: 2: 3: 4: 5: 6: 7: 8: 9: /// <summary> /// Constructor por defecto, incializa los elementos de la estructura /// interna del objeto. /// </summary> public Bolsa() { datos = new char[100]; cantidad = 0; }
Simplemente se inicializa el arreglo (lista en secuencia) y tambin el entero que mantiene la cantidad de elementos tiles dentro de la bolsa. Recordemos que los constructores no devuelven tipo, es ms ni siquiera se pone "void". Es importante corregir la declaracin del elemento "datos" en la estructura interna porque si la dejamos como est (private char[] datos = new char[100];) se inicializa dos veces lo que no es necesario, dado que estaramos reservando memoria en el heap que inmediatamente despus cuando se ejecuta el constructor se deja sin referenciar.
1: 2: 3: 4: 5: /// <summary> /// sirve para almacenar o guardar los elementos dentro del objeto /// inicialmente tiene una capacidad para 100 caracteres. /// </summary> private char[] datos;
Parece que codificar este constructor no agrega nada interesante porque su trabajo ya se haca en la declaracin de la estructura interna, lo que ocurre es que la intencin es implementar otros constructores (especializados) y por esa razn es que hace falta implementar el constructor por defecto. Por ejemplo, puede ocurrir que sea necesario contar con algn objeto del tipo Bolsa que siempre va a contener entre 5 a 10 elementos, de manera que asignar un "arreglo" de 100 elementos es un desperdicio. Esto se puede solucionar con un constructor especializado en el cual sea posible indicar el tamao inicial del arreglo.
1: /// <summary> 2: /// Constructor especializado, permite indicar el tamao o dimensin 3: /// del contenedor (arreglo) para los elementos de la Bolsa. 4: /// </summary> 5: /// <exception cref="ArgumentException"> 6: /// Dispara la excepcin de argumento invlido cuando el parametro es cero o menor 7: /// </exception> 8: /// <param name="dimension">Tamao incial del contenedor (debe ser mayor que cero)</param> 9: public Bolsa(int dimension) 10: { 11: if (dimension > 0) 12: { 13: datos = new char[dimension]; 14: cantidad = 0; 15: } 16: else 17: { 18: throw new ArgumentException("Argumento incorrecto, dimension = " + dimension.ToString()); 19: } 20: }
Ahora es posible "crear" un objeto del tipo Bolsa que incialmente tenga espacio para 10 elementos.
1: 2: Bolsa unabolsacomun = new Bolsa(); Bolsa unabolsachica = new Bolsa(10);
Otra posibilidad es que sea necesario "crear" un objeto del tipo Bolsa que sea una copia de otra bolsa. Esto es lo que se conoce como el Constructor Copia.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: /// <summary> /// Constructor especializado, permite crear un objeto como copia de otra /// </summary> /// <exception cref="ArgumentException"> /// Dispara la excepcin de argumento invlido cuando el parametro es nulo o /// no es del tipo Bolsa /// </exception> /// <param name="origen">objeto del tipo Bolsa que se copia</param> public Bolsa(Bolsa origen) { if ((origen != null) && (origen is Bolsa)) { datos = new char[origen.cantidad]; cantidad = origen.cantidad; for (int i = 0; i < cantidad; ++i) { datos[i] = origen.datos[i]; } } else { throw new ArgumentException("Argumento incorrecto, origen = null"); } }
Observen que las ltimas porciones de cdigo cuentan con instrucciones para controlar posibles errores que un programador puede cometer, como ser enviar un tamao (dimensin) igual o menor que cero, esto provocara un error en tiempo de ejecucin o en el otro caso tratar de crear un objeto del tipo Bolsa a partir de un objeto null (no inicializado) o un objeto que no es Bolsa.
Bien, podemos decir que se ha cumplido la segunda iteracin dado que tenemos un producto (en este caso un prototipo) que implementa todo el comportamiento que observamos cuando comenzamos el ejercicio. Es posible agregar elementos, sacarlos, averiguar si un elemento est en el objeto, contar cuantos elementos tiene el objeto, contar cuantos elementos de un valor en particular hay en el objeto, obtener una cadena con la representacin de los elementos que el objeto tiene, y otros mensajes que hicieron falta para completar y mejorar ese comportamiento. Obviamente el cdigo debe revisarse, seguramente hay quienes (saben algo ms de programacin) y piensan que puede implementarse de otra manera, con lo que estoy de acuerdo. Pero recordemos que esta publicacin trata de introducir a los "novatos" en la programacin orientada a objetos, dejemos la mejora del cdigo para otro momento. El siguiente paso o iteracin es convertir el prototipo en un producto que permita realizar todo ese comportamiento con elementos que vimos en el enunciado o dominio del ejercicio, por ejemplo artculos del supermercado.
Indudablemente, si estamos en programacin orientada a objetos un artculo es un objeto que tiene un comportamiento en particular, para comenzar podemos decir que hace falta que este objeto (artculo) pueda entregar una cadena con informacin sobre el artculo mismo y tambin que se pueda comparar un objeto del tipo artculo con otro (obviamente del mismo tipo) para ver si son iguales o no. En otras palabras necesitamos implementar los mensajes ToString y Equals. Como estructura interna de los objetos del tipo artculo vamos a considerar un entero que ser el cdigo del artculo, una cadena para la descripcin y un precio. Una forma de indicar toda esa especificacin es el siguiente artefacto (esto viene de UML)
Se est indicando los elementos de la estructura interna (privados) de cada objeto (por eso el signo "-" delante de cada elemento) tambin se indica los mensajes que cada objeto entiende, que de hecho son pblicos (por eso el signo "+" delante de cada mensaje). Para ser prolijos debemos agregar un elemento al proyecto, obviamente es una clase y el nombre apropiado es "Articulo", recuerden que las buenas prcticas de programacin dicen que los nombres de clases deben ser sustantivos y comienzan con la primer letra en maysculas. A continuacin tenemos el cdigo para esta clase.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: using using using using System; System.Collections.Generic; System.Linq; System.Text;
namespace DemoObjeto2 { public class Articulo { #region Campos de la Estructura Interna de cada objeto /// <summary> /// sirve para almacenar el cdigo de un objeto del tipo Articulo /// </summary> private int codigo; /// <summary> /// sirve para almacenar la descripcin del objeto del tipo Articulo /// </summary> private string descripcion; /// <summary> /// Sirve para almacenar el precio del objeto del tipo Articulo /// </summary> private decimal precio; #endregion
#region Constructores /// <summary> /// Constructor por defecto, inicializa la estructura interna del objeto /// </summary> public Articulo() { codigo = 0; descripcion = ""; precio = 0M; // El sufijo "M" indica que se trata de un decimal } /// <summary> /// Constructor especializado en el que se facilitan todos los valores /// mediante los parmetros /// </summary> /// <param name="codigo">cdigo del articulo</param> /// <param name="descripcion">descripcin del articulo</param> /// <param name="precio">precio del artculo</param> public Articulo(int codigo, string descripcion, decimal precio) { this.codigo = codigo; this.descripcion = String.Copy(descripcion); this.precio = precio; } /// <summary> /// Constructor especializado en el que se facilitan todos los valores /// mediante los parmetros /// Este constructor es para poder pasar parmetros del tipo double /// </summary> /// <param name="codigo">cdigo del articulo</param> /// <param name="descripcion">descripcin del articulo</param> /// <param name="precio">precio del artculo</param> public Articulo(int codigo, string descripcion, double precio) : this(codigo, descripcion, (decimal)precio) { } /// <summary> /// Constructor copia, inicializa un nuevo objeto igual al parmetro /// </summary> /// <param name="origen">Articulo que se copia</param> public Articulo(Articulo origen) : this(origen.codigo, origen.descripcion, origen.precio) { } #endregion #region Mtodos pblicos, implementan los mensajes /// /// /// /// /// <summary> Determina si el objeto de tipo Articulo es igual a otro Articulo </summary> <param name="obj">Objeto con el que se compara</param> <returns>Verdadero si los cdigos de ambos son iguales</returns>
El ejemplo de la clase Articulo es interesante para analizar y aprender. Utiliza String.Copy(...) porque de esa manera logramos que el elemento de la estructura interna de un objeto del tipo Articulo tenga su propia rea de memoria para la descripcin, si utilizramos el operador de asignacin "=" estaramos tomando solo la referencia. El otro punto interesante es como desde un constructor se invocan otro (lneas 62 y 71). El resto ya se comento en esta publicacin o en la anterior de la serie.
El cdigo para probar esto puede ser el siguiente (siempre es recomendable probar lo que estamos haciendo)
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: using using using using System; System.Collections.Generic; System.Linq; System.Text;
namespace DemoObjeto2 { class Program { static void Main(string[] args) { Articulo a = new Articulo(1, "uno", 15.0); Console.WriteLine("a : {0}", a.ToString()); Articulo b = new Articulo(2, "dos", 55.5); Console.WriteLine("b : {0}", b.ToString()); Articulo c = new Articulo(a); Console.WriteLine("c : {0}", c.ToString()); } } }
Ahora hay que modificar la implementacin de la clase Bolsa para que opere con objetos del tipo Articulo. Esto se puede hacer por partes, como se fue desarrollando el prototipo o (con un poco de coraje) cambiarlo todo de una sola vez. A continuacin esta el cdigo completo y luego las explicaciones que correspondan
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: using using using using System; System.Collections.Generic; System.Linq; System.Text;
namespace DemoObjeto2 { /// <summary> /// Definicin para aquellos objetos que presentan el comportamiento /// asociado con una "bolsa" que contine "elementos", en la bolsa se /// puede agregar o sacar elementos, tambine es posible averiguar si /// est vaca, o cuantos elementos tiene en total as como cuantos /// elementos de algun valor hay. /// /// Implementa la bolsa para objetos del tipo Articulo /// /// </summary> public class Bolsa { #region Campos de la Estructura Interna de cada objeto /// <summary> /// sirve para almacenar o guardar los elementos dentro del objeto /// inicialmente tiene una capacidad para 100 caracteres. /// </summary> private Articulo[] datos; /// <summary>
10
11
12
13
14
En primer lugar, el arreglo de la estructura interna ya no puede ser de "char" ahora debe ser de "Articulo"; esto se debe corregir en todos los lugares donde se declaran los arreglos (lneas 27, 44, 60, 81 y 109). Tambin se debe cambiar el tipo de los parmetros en Agregar, Contar, Existe y Sacar (lneas 105, 137, 156 y 173). Otras cuestiones son: En la lnea 86 (constructor copia) se debe crear un nuevo objeto a partir de otro, caso contrario ambas referencias apuntaran a la misma zona del heap; en cambio en la lnea 113 no hace falta porque se est redimensionando el arreglo de manera los objetos del viejo se pasan al nuevo y el Garbage Collector liberar solamente el espacio de la bolsa y no los de cada elemento (estos mantienen sus referencias en el nuevo arreglo). En las lneas 143, 160 y 178 se debe utilizar el mensaje Equals de los objetos del tipo Articulo para comparar con el parmetro (no es directo como con los caracteres). Finalmente en la lnea 187 (mtodo Sacar) hay que desreferenciar el ltimo elemento del arreglo para que el Garbage Collector pueda devolver esa memoria del Heap, caso contrario no tenemos acceso al elemento pero sigue estando referenciado.
15
namespace DemoObjeto2 { class Program { static void Main(string[] args) { Articulo a = new Articulo(1, "uno", 15.0); Console.WriteLine("a : {0}", a.ToString()); Articulo b = new Articulo(2, "dos", 55.5); Console.WriteLine("b : {0}", b.ToString()); Articulo c = new Articulo(a); Console.WriteLine("c : {0}", c.ToString()); Bolsa mibolsa = new Bolsa(); mibolsa.Agregar(a); Console.WriteLine("mibolsa tiene: {0}", mibolsa.ToString()); mibolsa.Agregar(b); Console.WriteLine("mibolsa tiene: {0}", mibolsa.ToString()); mibolsa.Agregar(new Articulo(45, "otro articulo", 345.6)); Console.WriteLine("mibolsa tiene: {0}", mibolsa.ToString()); Bolsa otrabolsa = new Bolsa(mibolsa); Console.WriteLine("otrabolsa tiene: {0}", mibolsa.ToString()); mibolsa.Sacar(b); Console.WriteLine("mibolsa tiene: {0}", mibolsa.ToString()); } } }
Con lo cual hemos cumplido con la tercera iteracin, tenemos un producto que implementa el comportamiento observado para artculos de un supermercado.
16
Ahora tenemos que hacer lo mismo para otros objetos: armas, personas, etc. Vaya a saber uno que ser lo que tengamos que meter dentro de la bolsa, lo que es seguro es que no es posible estar codificando a cada rato la implementacin de la Bolsa cada vez que nos cambian el tipo de elementos que hay que meter adentro. Eso queda para la siguiente parte del ejemplo. Espero que les sirva.
17