Curso de Programación en C# [Comenzando con C#] Fecha: 30/Abr/2005 (22 de Abril de 2005)
|
Clases
Lo primero que nos viene a la mente cuando leemos esto es ¿Qué es una clase? e inmediatamente después de eso nos preguntamos ¿Para qué sirve una clase?, bueno una definición de clase sería que es una representación abstracta de un objeto, pero esto nos deja con bastantes interrogantes, veamoslo en español, je, je, je, un objeto puede ser cualquier cosa, por ejemplo una casa, la clase es la reprentación abstracta del objeto, en este caso serian los planos de la casa, en ellos la casa se ve representada por completo, ya que en el plano podemos ver que tendrá la casa, donde lo tendrá, como estará hecho, más sin embargo no podemos sentarnos en las escaleras del plano de la casa porque la casa aún no existe, la clase es solo el plano del que después podremos construir el objeto, más sin embargo una clase por si sola no nos es de utilidad en lo absoluto hasta que la convertimos en un objeto, es decir no podemos habitar el plano de una casa, necesitamos construirla es decir construir el objeto, a dicho proceso de construcciòn se le llama instancia. Ejemplo: class Casa { decimal Altura; decimal Ancho; decimal Profundidad; string Color; void Entrar(); void Salir(); } Ahora tenemos un ejemplo mas formal de lo que sería una clase, en este ejemplo declaramos una clase llamada Casa, una Casa en la vida real tiene diversas caracteristicas o "Atributos", la clase guarda dentro de si tanto los atributos de la Casa como la forma en que se comportara, dicho comportamiento define como reaccionara ante otras clases. Una vez que se ha definido la clase Casa TODA clase que heredase de ella tendria todas las caracteristicas de nuestra Casa original.Objetos
Un objeto es la consolidación de una clase, retomando el ejemplo de la casa, deciamos que los planos de la Casa serian la Clase casa, es decir la representación abstracta del objeto miCasa, pero para que podamos utilizar la clase Casa necesitamos instanciarla, la instancia es el proceso de construir la Casa. Ejemplo: class Casa { decimal Altura; decimal Ancho; decimal Profundidad; string Color; void Entrar(); void Salir(); } Tomando como Base la Clase Casa ahora lo que haremos sera declarar miCasa del tipo Casa. Ejemplo: Casa miCasa = new Casa(); Quiza sea un poco dificil de captar al principio, pero en realidad es de lo mas sencillo, miren toda clase es un tipo de datos es decir, en el mundo real existen numeros no decimales menores a cierto tamaño, bueno en C# se creo una clase llamada int para representarlos de manera abstracta, es por esto que cada que declaramos un entero declaramos: int miNumero = 0; tipo nombre valor Pero porque usamos new y los (), bueno, porque en el caso de los enteros(int) simplemente contienen un dato y no hacen nada mas son simples contenedores, sin embargo las clases que a nosotros nos interesan y que son las que estaremos creando deben actuar de distintas maneras, ya sea leyendo bases de datos, actualizando paginas web, realizando operaciones matematicas, etc., es por esto que se utiliza el operador new, eso nos permite mandarle datos a la clase que hemos creado, supongamos que nuestra clase Casa acepta 3 parametros, Nombre del dueño, Precio y Si esta a la venta o no, bueno pues en ese caso podriamos inicializar nuestra casa asi: Casa miCasa = new Casa("Jorge","$1,408,506","SI"); Estos parametros de entrada provocan que el objeto "reaccione" realizando una serie de acciones, como por ejemplo, publicar la casa en un sitio web de venta de casas, es un ejemplo sencillo, pero que nos da una idea de como reaccionan los objetos. Una caracteristica de los objetos es que presentan siempre 3 caracteristicas: Identidad, Comportamiento y Estado. + Identidad: Nos permite distinguir a un objeto de los demás obejtos en la misma clase, por ejemplo imaginemos que tenemos una casa igual que la de nuestro vecino, como en los conjuntos habitacionales donde todas las casas son iguales en ese caso el Número de casa es lo que nos distingue, porque aunque tuvieran el mismo color, los mismos arreglos al frente, etc., 2 casas no pueden tener el mismo numero. + Comportamiento: Esto hace utiles a los objetos, ya que los objetos existen para hacer algo, si los objetos no hicieran nada no tendría chiste crearlos, el comportamiento hace accesible a un objeto, asi mismo los objetos que vienen de una misma clase comparten el mismo comportamiento, por ejemplo, una Casa es una Casa porque se puede habitar, un Carro es un Carro porque se puede manejar, etc. + Estado: Tiene que ver con el funcionamiento interno de una clase, dicho funcionamiento interno es el que le permite proporcionar su comportamiento. Un objeto bien diseñado mantiene inaccesible su estado, esto es porque a uno no le interesa como le hace un objeto para hacer lo que hace, a uno simplemente le interesa que lo haga. Para diseñar objetos es importantisimo tomar en cuenta lo siguiente: + Decidir lo que es importante y lo que no, es decir, enfocarse en lo que es importante y depender sólo de ello. + Ignorar lo que no es importante y no depender de ello, basandonos en el principio de Dependencia mínima + Utilizar la Encapsulación para aplicar la Abstracción. Es fácil quedar abrumado ante la inmensa cantidad de atributos y comportamientos que puede tener un objeto es por ello que es de vital importancia concentrarse tan solo en aquello que es importante, si en los planos se incluyeran detalles de donde pondrá la lavadora, la t.v., donde encadenará al perro, donde estará el clavo para sujetar su reloj, etc., sería increiblemente tedioso y abrumador el representarlo, es por esto que solo se debe uno concentrar en los detalles que REALMENTE importan, a eso se le llama abstracción. La abstracción es una técnica que nos permite quitarle a un objeto todos aquellos comportamientos que son innecesarios hasta que el objeto solo tiene dentro de si aquellos comportamientos que son esenciales para su funcionamiento, una buena abstracción elimina todos los detalles poco importantes y le permite enfocarse y concentrarse SOLO en los detalles importantes. Debemos recordar que un objeto deberá siempre aplicar el concepto de Dependencia mínima por motivos de encapsulación la dependencia mínima permite que las cosas complejas sean simples, esto se logra al ocultar los aspectos no esenciales de una clase, estos aspectos una vez ocultados, no se pueden ver ni utilizar.Encapsulación
+ Combinar datos y métodos en una entidad única + Controlar el acceso a los datos y los métodos - Público Accesible desde fuera - Privado Accesible solo desde adentro Pero ¿Porqué es útil encapsular?, sencillo, la encapsulación minimiza el impacto, es decir si algún día nos vemos en la necesidad de actualizar algunas clases de nuestro programa, entre menos dependamos de una clase menos impacto tendrá el hecho de que se cambie, así mismo nos permité esconder los detalles de implementación de nuestro programa, lo que aumenta su resistencia a ataques, esto debido a que al declarar metodos y/o variables como privadas las ocultamos y dejan de ser visibles para las demas clases.Creación de una Clase Sencilla
class Persona { public int Edad; public string Nombre; private string Direccion; } Lo que hacemos es declarar dos variables publicas que pueden ser accedidas sin problemas desde el exterior y una variable privada que solo puede ser utilizada por miembros de la clase, ahora para utilizar nuestra clase persona necesitamos crear una persona: Persona Alejandro = new Persona(); //Acabo de nacer, ¿como ven?, je, je, je Alejandro.Edad = 22; Alejandro.Nombre = "Alejandro Domingo Velazquez Cruz"; Recordemos que cada objeto se trata por separado, es decir por referencia, lo cambios que realicemos a cualquier otra persona que creemos no afectaran a Alejandro, por ejemplo: Persona Carmen = new Persona(); Carmen.Edad = 21; Carmen.Nombre = "María del Carmen Lopez Villegas"; Alejandro.Nombre sigue siendo Alejandro Domingo Velazquez Cruz y Carmen.Nombre es María del Carmen Lopez Villegas y los cambios que realicemos a cualquiera de ellos no afectan al otro en lo absoluto. Ahora veamos si nosotros pusieramos algo así: Alejandro.Direccion = "Norte 5"; El compilador marcaría error, ya que esa variable solo puede ser accedida desde adentro de la clase, ya sea por medio de algún método público o declarando un atributo público para permitir el acceso a ese dato, es decir, nosotros sabemos que existe la variable porque nosotros hemos hecho la clase, pero sin embargo no se puede acceder a esa variable, ni siquiera sabiendo que existe, a esto se le llama ocultamiento, ya que la variable queda "oculta" a las demás clases, podemos ocultar tanto variables como métodos e inclusive se pueden ocultar clases. Por último hemos aprendido a crear objetos, pero y ¿Comó destruirlos?, bueno C# hace uso de la recuperación automatica de memoría gracias a su Recolector de Basura, lo más que podemos hacer es marcar un objeto para indicar que ya puede ser recolectado, esto generalmente no es necesario, ya que el Recolector de Basura detecta cuando un objeto ya no está siendo utilizado porque no ahi ningún otro objeto que tenga su dirección, pero eso lo veremos después.Miembros estáticos versus instancias
+ Los miembros estáticos los comparten todas las instancias de una clase + Los miembros de instancia pertenecen a instancias específicas de una clase Es decir cuando declaramos una variable estática todos los objetos creados a partir de esa clase tendrán esa variable en común esto es útil por ejemplo en los sistemas bancarios, no es practico ni comodo el hecho de que cada objeto almacene el tipo de cambio es más practico que se declare estático y sea compartido por todos los objetos, ya que el tipo de cambio es el mismo para todos ellos, ejemplo: class Cliente { static decimal tipoCambio; } Esto quiere decir que en lugar de declarar la variable tipoCambio a nivel de objeto la estamos declarando a nivel de clase, en un lenguaje estructurado esto lo convertiría en una variable global, con sus respectivas ventajas y desventajas, que es accesible a todos, pero a cambio una variable global no puede ser encapsulada, sin embargo los lenguajes orientados a objetos no permiten las variables globales, sin embargo tienen lo que se conoce como variables estáticas. Los datos estáticos se declaran dentro de clase beneficiandose de la encapsulación de la clase, pero están asociados con la clase misma y no con cada objeto, es decir que aunque nunca se genere un objeto esa variable ocupará espacio. Puede haber tanto variables como métodos estáticos y son compartidos por todas las instancias de la clase, es decir, son comunes a todos los objetos generados a partir de dicha clase. Ahi que tomar en cuenta algo muy importante Cuando un método se declara estático, dicho método SOLO es accesible a travéz de la clase y no a travéz de las instancias de la clase los métodos estáticos solo pueden ser accesados por los datos estáticos y por otros métodos estáticos, además debido a que son parte de la clase pueden ser accesados sin crear una instancia de la misma. En C# no se puede acceder a un método estático desde una instancia. public class Cliente { public string nombreCliente; public void consultaNombreCliente() { Console.WriteLine(nombreCliente); } public static string nombreBanco; public static void consultaNombreBanco() { Console.WriteLine(nombreBanco); } } Cliente miCliente = new Cliente(); miCliente.nombreCliente = "Alejandro Velazquez"; //Definó el nombre del cliente miCliente.consultaNombreCliente(); //Esto imprimirá el nombre del cliente Cliente.nombreBanco = "BANAMEX"; //Definó el nombre del banco Cliente.consultaNombreBanco(); //Esto imprimirá el nombre del banco El nombre del banco y la consulta al nombre del banco se realizan directamente a travéz de la clase, no pueden realizarse a travéz de las instancias de la clase, esto debido a que son variables estáticas, sin embargo el nombre del cliente y la consulta al nombre del cliente se realizan a travéz de las instancias de la clase, esto es especialmente útil debido a que en este ejemplo el nombre del banco es algo común a todos los clientes, porque todos son clientes del mismo banco, sin embargo, cada cliente tiene su propio nombre, así que lo que hicimos es declarar el nombre del banco y la consulta al nombre del banco estáticas con esto el dato se guarda una única vez en la clase y solo puede ser accedida a travéz de la clase, así mismo declaramos normalmente el nombre del cliente y la consulta al nombre del cliente ya que cada que creemos un objeto cliente será una persona diferente con un nombre diferente y por lo tanto el valor del campo cambiará en cada instancia. Ahora veamos, hemos visto distintas formas de encapsular y manipular los datos, pero ¿Qué pasa si necesitamos acceder a un método o dato privado desde afuera?, bueno para eso existen las propiedades y son realmente utiles, las propiedades nos permiten manipular datos privados desde afuera de la clase, ejemplo: class Persona { public string nombreCompleto() { Console.WriteLine(Nombre+" "+Apellido); } public string nombre { get{ return Nombre; } set{ Nombre = value ; } } public string apellido { get{ return Apellido; } set{ Apellido = value; } } private string Nombre,Apellido; } En este ejemplo se declaran dos variables privadas, Nombre y Apellido si intentaramos acceder a dichas variables desde el exterior de la siguiente manera: Persona Alejandro = new Persona(); //Ja, ja, ja he vuelto a nacer... ;-) Alejandro.Nombre = "Alejandro"; Alejandro.Apellido = "Velazquez"; Esto nos marcaría un error ya que dichos datos han sido declarados como privados y por tanto son invisibles desde afuera de la clase, lo que podemos hacer en cambio es accederlos a travéz de propiedades de la siguiente manera: Alejandro.nombre = "Alejandro"; Alejandro.apellido = "Velazquez"; Alejandro.nombreCompleto(); El ejemplo se ve muy similar al anterior, pero ahí una sútil diferencia. public string nombre //El nombre de la propiedad se parece al de la variable, solo cambia la primera letra, esto la hace fácil de recordar { get{ return Nombre; } //get nos permite recuperar el valor de la variable privada set{ Nombre = value ; } //set nos permite modificar el valor de la variable privada } La propiedad llamada nombre nos permite modificar el valor de la variable privada llamada Nombre, pero va mas alla de eso, gracias a las propiedades podemos validar los datos y asegurarnos que son confiables antes de volcarlos sobre nuestras variables privadas y viceversa, asegurarnos que va a recibir el valor de la variable privada alguien que nosotros queremos que reciba el valor de la variable privada. Además las propiedades nos ayudan a proporcionar encapsulación. Ejemplo: class Persona { public string nombreCompleto() { Console.WriteLine(Nombre+" "+Apellido); } public int edad { get { return Edad; } set { if( 0 < value && value < 100) //validamos que sea una Edad valida de acuerdo a nuestro criterio Edad = value; //De ser asi modificamos el valor de la variable privada } } public string nombre { get{ return Nombre; } set{ Nombre = value ; } } public string apellido { get{ return Apellido; } set{ Apellido = value; } } private int Edad; private string Nombre,Apellido; } Utilizando el ejemplo anterior hemos agregado una variable nueva llamada Edad de tipo entero, ya que las edades no pueden ser negativas y como estamos asumiendo que ninguno de es casi imposible que nos topermos con una persona de 100 o mas años, pues condicionamos la propiedad para que solo acepte valores enteros que sean mayores que 0 y menores que 100, esta es un sencillo ejemplo de como utilizar las propiedades para validar las entradas suministradas por el usuario, esto es sumamente útil ya que nos ahorra muchos errores en tiempo de ejecución, así mismo nos permite tener una aplicación más segura.Alcance
El alcance delimita que regiones de código pueden referenciar un elemento del programa, los modificadores de acceso son los que definen el alcance de los miembros de la clase y son: Capacidad de Acceso Significado public El acceso no se encuentra restringido protected El acceso está limitado a la clase actual, así como a las clases que hereden de ella internal El acceso está limitado al proyecto actual internal protected El acceso está limitado al proyecto actual, así como a los tipos derivados de ella private El acceso está limitado a la clase actual Reglas Los espacios de nombre siempre son públicos (implícitamente). Las clases siempre son públicas (implícitamente). Los miembros de clase son privadas por predeterminación. Todo esto a menos que se agregue un modificador de alcance para variar la capacidad de acceso el elemento. Recomendaciones Solo deben hacerse públicos aquellos datos que los usuarios de su clase necesiten ver, aunque siempre es más recomendable en medida de lo posible el uso de atributos para acceder a dichos valores, NO DECLARE COMO PUBLICA UNA VARIABLE A MENOS QUE SEA E-S-T-R-I-C-T-A-M-E-N-T-E necesario. Para los efectos del curso se tratarán las variables de una manera no completamente segura por razones de legibilidad del código, pero una vez que obtengan más experiencia deben evitarlo.Constructores y Destructores
Los constructores nos permiten inicializar la memoria y convertirla en un objeto listo para usarse, pero ahi ciertas reglas: + El Constructor Inicializa la memoria y la convierte en un objeto utilizable + Si no se especifica un constructor: - El compilador genera un constructor predeterminado - Accesibilidad pública - Mismo nombre que el de la clase - Sin tipo de retorno, ni siquiera null - No espera argumentos - Inicializa todos los campos en cero, falso o nulo + Si se especifica un constructor - Se invoca cuando se inicializa el objeto - Se puede sobrecargar Caracteristicas del Constructor Predeterminado: + Todos los campos se inicializan con cero - Campos numéricos (int, double, decimal, etc..) se inicializan a cero. - Campos bool se inicializan en false. - Tipos de referencia(como los objetos) se inicializan en null. - Tipos struct se inicializan para contener valores de cero. + Accesibilidad pública No siempre es recomendable el utilizar el constructor predeterminado, por ejemplo, no siempre es bueno el hecho de que el acceso sea público o quizás sea un problema el hecho de que se inicialicen a 0 ciertos valores, así mismo el código invisible es dificil de mantener, el código del constructor predeterminado es invisible. Debemos recordar que aunque los constructores son tipos especiales de métodos eso no implica que no puedan ser sobrecargados. Ejemplo: class Persona { public Persona( ) //Constructor personalizado { Nombre = ""; Apellido = ""; Console.WriteLine("Objeto Construido"); } public string ObtenerNombreCompleto() { return ( Nombre+" "+Apellido ); } public string nombre { get{ return Nombre; } set{ Nombre = value ; } } public string apellido { get{ return Apellido; } set{ Apellido = value; } } private string Nombre,Apellido; } Algo importante es que podemos modificar la accesibilidad de un constructor. Constructores privados: Se utilizan para evitar que los objetos se creen a partir de una clase especifíca, imaginemos que desea crear una clase que provea cierta funcionalidad, pero no desea que los usuarios de esa clase puedan crear objetos con base en ella, bueno en este caso ponemos metodos y variables estáticas dentro de la clase, en este caso ya no es necesario crear instancias de la clase (objetos), sin embargo el constructor predeteminado es public por predeterminación, lo cual implica que SI se pueden crear instancias de esa clase, aunque dichos objetos no tendrán ningún sentido ya que la clase está compuesta únicamente de métodos y variables estáticas, para evitar esto basta con modificar la accesibilidad del constructor haciendolo private impidiendo que se creen instancias de la clase. Al declarar un constructor C# no generará ningún constructor y al hacerlo privado evita que se creen instancias de esa clase. Constructores estáticos: Se emplean para asegurarse de que una clase siempre es inicializada antes de utilizarse, esto nos asegura que la clase se encuentra en un estado inicial bien definido antes de utilizarse. Destructores y Terminadores Provocan que las acciones finales de los objetos sean diferentes y no pueden ser determinadas por recolección de residuos, así mismo todos los objetos en la plataforma .NET tienen un método de Finalizar, si está presente, la recolección de residuos llamará al Destructor antes de reclamar la memoria binaria, en C# no se puede llamar o sobrescribir Object.Finalize. Un destructor es un mecanismo de limpieza de memoria. Finalización Cuando el recolector de basura libera los recursos utilizados por un objeto no referenciado, verifica primero si la clase del objeto no tiene un destructor o método Finalize propio, si la clase cuenta con uno el será invocado antes de reciclar la memoría a la pila. Las instrucciones escritas en el destructor o método Finalize son únicamente para esa clase. Destructores En C# el método Finalize no está disponible de manera directa y no puede ser invocado y/o cancelado, se deberá colocar código para que sea ejecutado durante la finalización dentro de un estructor, dicho destructor tendrá el nombre de la clase antecedido por el simbolo ~. Ejemplo: Puede escribir un destructor para implementar la limpieza de un objeto. En C#, el método Finalize no está disponible directamente, y no puede invocar o cancelar el método Finalize. Debe colocar código para que sea ejecutado durante la finalización dentro de un destructor. class Persona { public Persona( ) //Constructor personalizado { Nombre = ""; Apellido = ""; Console.WriteLine("Objeto Construido"); } ~Persona() { Console.WriteLine("Objeto Destruido"); } public string ObtenerNombreCompleto() { return ( Nombre+" "+Apellido ); } public string nombre { get{ return Nombre; } set{ Nombre = value ; } } public string apellido { get{ return Apellido; } set{ Apellido = value; } } private string Nombre,Apellido; } Bueno, eso es todo por este capitulo, espero verlos en el siguiente donde trataré temas como herencia y polimorfismo entre otros...AVISO: Impartiré unos cursos en linea TOTALMENTE GRATUITOS en la página http://groups.msn.com/MUGVeracruz