Conceptos Básicos de la Programación Orientada a Objetos con Java Dado que Java es un lenguaje orientado a objetos, es imprescindible entender qué es esto y en qué afecta a nuestros programas. Desde el principio, la carrera por crear lenguajes de programación ha sido una carrera para intentar realizar abstracciones sobre la máquina. Al principio no eran grandes abstracciones y el concepto de lenguajes imperativos Pascal, C, y otros, son prueba de ello. Exigía pensar en términos de la máquina y no en términos del problema a solucionar. Esto provocaba que los programas fueran difíciles de crear y mantener, al no tener una relación obvia con el problema que representaban. Muchos paradigmas de programación intentaron resolver este problema alterando la visión del mundo y adaptándola al lenguaje. Estas aproximaciones modelaban el mundo como un conjunto de objetos. Esto funcionaba bien para algunos problemas pero no para otros. Los lenguajes orientados a objetos, más generales, permiten realizar soluciones que, leídas, describen el problema. Permiten escribir soluciones pensando en el problema y no en la máquina que debe solucionarlo en último extremo. Las características más importantes son: 1. Abstracción. Se puede definir la abstracción como la propiedad que permite representar las características esenciales de un objeto. Se centra en la vista externa de un objeto y sirve para separar el comportamiento de un objeto de su imp1ementación. 2. Encapsulamiento. Es como un envoltorio protector alrededor del código y los datos que se manipulan. El envoltorio define el comportamiento y protege el código y los datos para evitar que otro código acceda a ellos de manera arbitraria. El poder es que todo el mundo conoce como acceder a él y pueden utilizarlo de este modo independientemente de los detalles de implementación. 3. Herencia. Objetos que se relacionan entre ellos de una manera jerárquica. Es decir a partir de una clase donde están los atributos generales (superclase) se definen otras clases con atributos más específicos (subclase). 4. Polimorfismo. A los métodos que actúan sobre los objetos se les pasa información. Estos parámetros representan los valores de entrada a una función. El polimorfismo significa que un método tenga múltiples implementaciones que se seleccionan en base a que tipo de objeto se le pasa. Cómo maneja Java los cuatro conceptos de la POO? 1.- Abstracción La abstracción es una forma de trabajar con la complejidad que nos impone el mundo real. Una abstracción denota las características esenciales de un objeto que lo distinguen de todos los demás tipos de objeto y proporciona así fronteras conceptuales nítidamente definidas respecto a la perspectiva del observador. Nos permite separar el comportamiento de la implementación. Será más importante saber qué se hace, y no cómo se hace. Por ejemplo: Un sensor de temperatura. Sabemos que Mide la temperatura actual y la dice Se puede calibrar No sabemos Como la mide? De qué está hecho? 1
Clases y métodos abstractos Java maneja abstracción a través de clases abstractas y de métodos abstractos. Muchas veces, al estar diseñando un sistema de información, terminamos creando una familia de clases con una interfaz común. En estos casos es posible decir que la clase raíz de las demás no sea una clase útil, y que hasta deseemos que el usuario nunca haga instancias de ella, porque su utilidad es inexistente. No queremos implementar sus métodos, sólo declararlos para crear una interfaz común. Entonces declaramos sus métodos como abstractos: public abstract void mimetodo(); Como vemos, estamos declarando el método, pero no implementándolo, ya que sustituimos el código que debería ir entre llaves por un punto y coma. Cuando existe un método abstracto deberemos declarar la clase abstracta o el compilador nos dará un error. Al declarar como abstracta una clase, nos aseguramos de que el usuario no pueda crear instancias de ella. En este caso, estamos llegando a un nivel superior de abstracción, lo cual es bastante deseable en muchas ocasiones. Por ejemplo, suponiendo que tenemos varias subclases de la clase Mamífero y aún más; las únicas que se ocupan son las subclases (la clase Mamífero solo se ocupa como un "molde" de la que se pueden sacar otras clases). Entonces podemos mirar a la clase Mamífero como una clase Abstracta, de la cual se pueden derivar una familia de clases. Ejemplo de clase abstracta // Mamifero.java abstract class Mamifero String especie, color; public abstract void mover(); // Gato.java class Gato extends Mamifero int numero_patas; public void mover() System.out.println("El gato es el que se mueve"); // DemoAbstracta.java class DemoAbstracta public static void mainstring[] args) Gato felix = new Gato(); felix.mover(); 2
2.- Encapsulamiento En Java la base del encapsulamiento es la clase, (conjunto de objetos que comparten la misma estructura y comportamiento). Una clase se define mediante métodos que operan sobre esos datos. El método es un mensaje para realizar alguna acción en un objeto. Al crear un objeto de una clase dada, se dice que se crea una instancia de la clase, o un objeto propiamente dicho. Por ejemplo, una clase podría ser Autos, y un auto dado, por ejemplo, Audi, es una instancia de la clase. La ventaja de esto es que como no hay programas que actúan modificando al objeto, éste se mantiene en cierto modo independiente del resto de la aplicación. Si es necesario modificar el objeto (por ejemplo, para darle más capacidades), esto se puede hacer sin tocar el resto de la aplicación, lo que ahorra mucho tiempo de desarrollo, mantención y depuración. Para tener un control de cual o cuales datos se deben de ocultar o establecer el nivel de privacidad o seguridad de los métodos y atributos definidos en una clase, se han dispuesto las siguientes palabras reservadas: final. Indica que el método o el atributo no podrá ser modificado en las subclases de la clase en la cual se declare el método, esto se hace para evitar problemas en errores de implementación. static. Indica que un atributo o un método será global y único para toda la clase. En cualquier caso para usar un atributo o un método estático, se tiene que poner primero el nombre de la clase a la que pertenecen y luego el nombre del atributo o el del método. private. Indica que un método o un atributo sólo puede ser cambiado o accedido desde la clase en la que está definido. Esto sirve para defmir métodos u atributos internos a los métodos y que son de uso interno. protected. Indica que un método o atributo puede ser accedido desde cualquiera de las subclases de la clase en la que se han definido, su funcionamiento es similar al del modificador private, la diferencia que permite acceso a las subclases. public Indica que un atributo o un método puede ser accedido desde cualquier implementación de una clase. Ejemplo de encapsulamiento Supongamos que debemos programar una clase que represente una figura, a la cual se piensa calcular su área y su perímetro. La solución que presento para este problema hace uso total del concepto de encapsulación, y es la siguiente: // Figura. java class Figura // Atributos en la clase. private double ancho; private double alto; private double perimetro; 3
private double area; // Método constructor. public Figura(double ancho, double alto) setancho(ancho); setalto(alto) ; setperimetro(0); setarea(0); // Setear el ancho de la figura. public void setancho(double ancho) this.ancho = ancho; // Setear el alto de la figura. public void setalto(double alto) this.alto = alto; //Setear el perímetro de la figura. public void setperimetro(double perímetro) this.perimetro = perímetro; //Setear el área de la figura. public void setarea(double area) this.area = area; //Llamar a setperimetro). public void perimetro() setperimetro(0); // Llamar a setarea). public void area() setperimetro(0); // Obtiene el ancho de la figura. public double getancho() return (ancho); // Obtiene el alto de la figura. 4
public double getalto() return (alto); // Obtiene el perímetro de la figura. public double getperimetro() return (perimetro); // Obtiene el área de la figura. public double getarea() return (area); El concepto de encapsulamiento está totalmente inmerso en esta clase. Puesto que tanto los datos los atributos (ancho, alto, perimetro y area) como los métodos, los he enfrascado en una clase, la que he llamado Figura. Debido a que los datos miembros de esta clase son private, entonces sólo es posible acceder ellos mediante los métodos de la misma clase y de una forma bien definida. Lo que hacemos en la clase Figura es encapsular datos y métodos, protegiéndolos así de un acceso indebido por parte de posibles clientes no deseados. Además, como todos los métodos en la clase Figura son del tipo de acceso public, entonces el objeto instanciado de esta clase podrá comunicarse a través de estos métodos sin ningún tipo de problemas. Es decir, dejando que los métodos sólo puedan acceder en forma real a los datos y no de otra forma. 3.- Herencia Herencia es la definición de una clase en función de otra, esto es definir clases con características similares pero con alguna diferencia. La herencia permite que una clase tenga inicialmente el mismo comportamiento que otra y luego aumente o modifique este comportamiento según corresponda. La nueva clase (clase hija) que hereda los atributos y métodos de otra clase ya existente, se conoce como subclase y a la clase padre se le conoce como superclase; adicionalmente una subclase puede ser una superclase. Podemos ver a una subclase como la especialización de su superclase, ya que esta tiene atributos y métodos comunes, mas atributos y métodos adicionales, junto con un comportamiento diferente en la ejecución de algunos métodos comunes. Cada nueva subclase sólo describe los atributos y métodos adicionales así como la implementación de algunos métodos cuya ejecución no se desea que sea igual a la de su superclase. Principalmente existen dos tipos de herencia: la simple que significa que una clase hereda las características de otra y la herencia múltiple significa que una clase hereda de dos o más clases. En Java se soporta la herencia simple pero no la múltiple, aunque existe un mecanismo que permite tener características de herencia múltiple. La herencia simple indica que una clase hereda todos los métodos y atributos de otra. Para indicar esto es necesario usar la palabra reservada extends. En general class NombreDeClaseHija extends NombreDeClasePadre 5
Ejemplo de herencia Veamos un ejemplo sencillo. Sabemos bien que un cuadrilátero es una figura. Sin embargo es también un concepto más específico, lo que significa que puede crearse una clase Cuadrilatero, la que comparte con Figura propiedades y métodos, pero también puede tener algunas propias. Nosotros, como buenos programadores que somos, nos fijamos que no es necesario volver a escribir otra vez las operaciones implementadas anteriormente en la clase Figura. Lo que debemos hacer es solamente programar las nuevas operaciones, encapsularlas en una clase y esta nueva clase derivarla de la superclase Figura. // Cuadrilatero.java class Cuadrilatero extends Figura // Método constructor que inicializa los datos miembros public Cuadrilatero(double ancho, double alto) super (ancho, alto); // Calculamos y seteamos el perímetro para el cuadrilátero public void perimetro() setperimetro (2*getAncho () + 2*getAlto()); // Calculamos y seteamos el área para el cuadrilátero public void area() setarea(getancho()*getalto()); // DemoHerencia.java class DemoHerencia public static void main(string[] args) // Definimos un objeto Cuadrilatero cuad; // Lo creamos cuad = new Cuadrilatero(70, 20); // Calculamos perímetro y área. cuad.perimetro(); cuad.area(); // Imprimimos sus resultados. System.out.println("Perimetro = " +cuad.getperimetro()+ ""); System.out.println("Area = " +cuad.getarea()+ ""); 6
En la implementación de la clase Cuadrilatero se han definido e implementado los dos método adicionales: perímetro() y area(). Notar que al definir la clase se utiliza la palabra reservada extends, seguido por el nombre de la clase Figura. Esto nos permite decir que se deben heredar todos los atributos y métodos de la superclase Figura a la subclase Cuadrilatero. Fijarse en el detalle de que generalmente una subclase es una especialización de la superclase. Otro detalle importante es que en una subclase se puede implementar métodos que tengan el mismo nombre que los de su clase madre (incluso con los mismos parámetros), permitiendo así llevar a cabo el otro concepto de la POO: el Polimorfismo. Ejecución de métodos de la superclase (super) Hay ocasiones en que se desea ejecutar el método de la superclase (clase madre) desde una subclase y además de modificar la implementación del mismo método en la subclase. Esto es usar el mismo código de la superclase mas un código particular para la subclase. Para referirse a la superclase de una clase se puede usar la palabra reservada super. Esta palabra se utiliza como si fuera un método de esa clase, con la diferencia que invoca a los métodos de la superclase. En el constructor de la clase Cuadrilatero he llamado al constructor de la clase madre haciendo uso de esta palabra reservada super()con los parámetros que son necesarios para ejecutar el constructor de la superclase Figura. 4.- Polimorfismo El polimorfismo es la capacidad de los objetos de una misma jerarquía de clase responder de manera diferente a un mismo requerimiento, en este contexto, método. Esto se logra al reescribir el código de un método heredado en una subclase de su superc1ase, con esto se tienen clases con el mismo método pero con diferentes implementaciones. Hay que tener en cuenta que una instancia de una subclase también se considera como instancia de la superclase, ya que tiene los atributos y métodos de la superclase. El polimorfismo alivia el problema de estar creando múltiples métodos con distintos nombres para funciones conceptualmente similares. Ejemplo de Polimorfismo En muchas ocasiones, cuando utilizamos herencia podemos terminar teniendo una familia de clases que comparten un interfaz común. Por ejemplo, si creamos un nuevo archivo que contenga a Figuras y Cuadrilatero le podríamos añadir, por ejemplo una clase elipse. //Elipse. java class Elipse extends Figura // Método constructor. public Elipse(double ancho, double alto) super (ancho, alto); // Calcula el perímetro para una elipse. 7
public void perimetro() setperimetro(math.pi*(getancho() + getalto())); // Calcula el área para una elipse. public void area() setarea(math.pi*(getancho()*getalto())); //testeando el polimorfismo class DemoPolimorfismo public static void main(string[] args) Cuadrilatero cuad = new Cuadrilatero(100,20); Elipse elip = new Elipse(100,20); System.out.println("Perímetro Cuadrilátero es = "+ perimetro(cuad)+""); System.out.println("Perímetro Elipse es = "+ perimetro(elip)+""); System.out.println("Area Cuadrilátero es = " + area(cuad)+""); System.out.println("Area Elipse es = "+ area(elip)+""); public static double perimetro(figura x) x.perimetro(); return (x.getperimetro()); public static double area(figura x) x.area () ; return (x.getarea()); Vemos que el método static perímetro() llama al método perímetro() de una Figura. Y este sabe exactamente de qué clase de figura se trata. Sobrecarga de métodos, a propósito del Polimorfismo. El concepto de po1imorfismo, en cuanto a cambio de forma, se puede extender a los métodos. Java permite que varios métodos dentro de una clase se llamen igual, siempre y cuando su lista de parámetros sea distinta. Por ejemplo, si tuviéramos un método que sirviera para sumar números pero sin indicar de qué tipo: 8
//Sumar. java class Sumar public float suma(float a, float b) System.out.println("Estoy sumando reales"); return (a+b); public int suma(int a, int b) System.out.println("Estoy sumando enteros"); return (a+b); //DemoSobrecarga. java class DemoSobrecarga public static void main(string[] args) float x = 1, y = 2; int v = 3, w = 5; Sumar s = new Sumar(); System.out.println(s.suma(x,y));// float-s System.out.println(s.suma(v,w)); // int-s Esto también se aplica a los constructores. De hecho, es la aplicación más habitual de la sobrecarga. Por ejemplo, el siguiente programa hace uso de la sobrecarga en constructores. // Punto. java class Punto int x, y; public Punto(int a, int b) x = a; y = b; 9
public Punto () x = -1; y = -1; // DemoConstructores.java class DemoConstructores public static void main(string args[]) Punto p1 = new Punto(); Punto p2 = new Punto(10,20); System.out.println("(" +p1.x+ "," +p1.y+ ")"); System.out.println("(" +p2.x+ "," +p2.y+ ")"); Herencia múltiple: Las Interfaces Una interface es un conjunto de declaraciones de métodos (sin definición de ellos). También puede definir constantes, que son implícitamente public, static y final, y deben siempre inicializarse en la declaración. Todas las clases que implementan una determinada interface están obligadas a proporcionar una definición de los métodos de la interface, y en ese sentido adquieren una conducta o modo de funcionamiento. Una clase puede implementar una o varias interfaces. Para indicar que una clase implementa una o más interfaces se ponen los nombres de las interfaces, separados por comas, detrás de la palabra implements, que a su vez va siempre a la derecha del nombre de la clase o del nombre de la superclase en el caso de herencia. Por ejemplo class NombreDeClase implements Nombrelnterface, OtraInterface Ejemplo Interface Una interface se define de un modo muy similar a las clases. A modo de ejemplo se reproduce aquí la definición de la interface Mamífero y las clases Gato y Perro que la implementan: //Mamifero.java interface Mamifero public String color = ""; public float peso = 0; public void mover(); 10
//Gato.java class Gato implements Mamifero int numeropatas; public void mover() System.out.println("El gato es el que se mueve"); //Perro. java class Perro implements Mamifero int numeropatas; public void mover() System.out.println("Ahora el perro se mueve"); //DemoInterface.java class DemoInterface public static void main(string[] args) Gato felix = new Gato(); Perro patan = new Perro(); felix.mover(); patan.mover(); Cada interface public debe ser definida en un archivo *.java con el mismo nombre de la interface. Los nombres de las interfaces suelen comenzar también con mayúscula. Las interfaces no admiten más que los modificadores de acceso public y package. Si la interface no es public no será accesible desde fuera del package. Los métodos declarados en una interface son siempre public y abstract, de modo implícito. 11
Herencia múltiple Como se mencionó anteriormente: una clase no puede heredar de dos clases abstract, pero sí puede heredar de una clase abstract e implementar una ínterface, o bien implementar dos o más interfaces. Es decir, que con el uso de interfaces podemos lograr la herencia múltiple en Java. Examinemos el siguiente ejemplo: //PuedeMoverse. java interface PuedeMoverse public void mover(); // PuedeNadar.java interface PuedeNadar public void nadar(); // Mamifero.java class Mamifero String especie; float peso; public void mover() System.out.println ("El mamifero se mueve"); // Gato.java class Gato extends Mamifero implements PuedeMoverse, PuedeNadar int numeropatas; public void mover() System.out.println("El gato es el que se mueve"); public void nadar() System.out.println("El gato sabe nadar"); // DemoHerenciaMultiple.java class DemoHerenciaMultiple public static void main(string[] args) 12
Gato misifu = new Gato(); misifu.mover(); misifu.nadar(); Vemos que Gato tiene la obligación de implementar los métodos mover() y nadar(), ya que si no lo hace provocará un error de compilación. Podría no implementar mover(), ya que hereda su implementación de Mamífero. Pero si decidiéramos no hacerlo no habría problemas, ya que tomaría la implementación de su clase padre, ya que las interfaces no tienen implementación. Desde luego que aquí se han mostrado una serie de ejemplos en donde las clases no obedecen a una problemática o propuesta, pues de lo contrario debería llevarlos al documento Ejemplo de Análisis Orientado a Objetos, no obstante todo esta forma de relacionarse y el vocabulario utilizado es fundamental para entender la ingeniería de software orientada a objetos y su modelamiento orientado a objetos a través de UML. 13