Tema 3: Propiedades básicas de la Orientación a Objetos
Aprende sobre conceptos clave como abstracción, encapsulamiento, modularidad, jerarquía, polimorfismo, tipificación y ligadura dinámica.
1. Modularidad
Propiedad que tiene un sistema que ha sido descompuesto en un conjunto de partes o módulos que sean cohesivos (guardan cierta relación lógica) y débilmente acoplados (minimizan sus dependencias).
Durante este bloque utilizaremos este proyecto de ejemplo creado en el IDE InteliJ. Poco a poco iremos desglosando como funciona Java y la programación Orientada a Objetos.
Directory.idea/ directorio del IDE (podemos ignorarlo)
- …
Directoryout/ los archivos de compilación (resultado de compilar tu código)
- …
Directorysrc/ nuestro código fuente
Directorygraficos/
- Circulo.java
- …
Directoryotropaquete/
- Dibujante.java
- …
Directorytest/ nuestros tests (no se verá en este tema)
- …
- .gitignore
- Un paquete es
a un directorio - Es una agrupación lógica de clases.
- Las clases públicas son solo visibles dentro del paquete.
- Un fichero
a una clase - Dentro de un fichero puede haber más de una clase, pero solo una clase pública y esa fichero tiene que llamarse igual que la clase.
public class Circulo
enCirculo.java
.
- Dentro de un fichero puede haber más de una clase, pero solo una clase pública y esa fichero tiene que llamarse igual que la clase.
Para importar el paquete y poder usar las clases de otro paquete empleamos import graficos.*;
.
2. Jerarquía
Esta es la estructura del proyecto que usaremos durante este bloque
Directory.idea/
- …
Directoryout/
- …
Directorysrc/
- Direccion.java
- Estudiante.java
- Persona.java
- Profesor.java
- .gitignore
Composición
Cada clase debe ser responsable de la información que contiene. En una clase persona, cada persona tiene almacenada una dirección. Guardada como un objeto Direccion
que contiene el método toString()
para devolver la información almacenada como un String
.
Herencia
En Java una subclase hereda los métodos y atributos de su clase padre.
Por ejemplo tanto un Estudiante
como un Profesor
son Personas
por lo tanto heredan sus características y añaden sus propias.
Podríamos representarlo como:
Clases abstractas
El objetivo de las clases abstractas es agrupar características comunes a varias clases, pero es una clases de la que no se pueden crear instancias.
Todos los animales tiene características comunes, pero no hay ningún animal que sea simplemente animal.
public abstract class Animal { private String nombre; private String codigo; private Date fechaNacimiento;
public String getNombre() { return nombre; }}
public class Gato extends Animal { public enum variedadGato {SIAMES, ESFINGE};}
public class Perro extends Animal { public enum variedadPerro {PASTOR_ALEMAN, LABRADOR};}
También podemos usar los métodos abstractos para mantener la consistencia a lo largo de un programa.
public abstract class Figura { public abstract double area(); public abstract double perimetro();}
public class Circulo extends Figura { private double radio; public double area() { return Math.PI*radio*radio; } public double perimetro() { return 2*Math.PI*radio; }}
class Rectangulo extends Figura { private double base; private double altura; public double area() { return base*altura; } public double perimetro() { return (base*2)+(altura*2); }}
Interfaces
Las interfaces són similares a las clases abstractas. Se declaran con la palabra reservada interface
y contienen solo las cabeceras de los métodos.
Por defecto en las interfaces:
- Los métodos son públicos y abstractos.
- Los atributos públicos, estáticos y finales. Es decir, constantes de clase.
Rescatando el ejemplo anterior de las figuras, estas dos implementaciones són equivalentes:
public abstract class Figura { public abstract double area(); public abstract double perimetro();}public interface Figura { double area(); double perimetro();}
Una utilidad de las interfaces era la herencia múltiple:
interface Monstruo { void amenaza();}interface MonstruoPeligroso extends Monstruo { void destruye();}interface Letal { void mata();}interface Vampiro extends MonstruoPeligroso, Letal { void bebeSangre();}class Virus implements Monstruo, Letal { public void amenaza() { // Contagio del virus } public void mata() { // Enfermedad del virus }}
Código diagrama
flowchart BT Vampiro --> Letal & MonstruoP[Monstruo Peligroso] MonstruoP --> Monstruo Virus -.-> Letal & Monstruo
Herencia vs. composición
Composición ganó la batalla pero como en Java hay que mantener la retrocompatibilidad simepre, pues nadie tocó nada y se siguen pudiendo usar las dos.
Por ejemplo, en la API de Java, Stack
fué definido como un Vector
. Pero ArrayList
es la implementación moderna de Vector
. Ahora ya no se puede cambiar porqué romperíamos la retrocompatibilidad de Java.
Por lo tanto la composición es la clara vencedora, ya que una implementación de métodos abstractos es mucho mejor que heredar forzadamente todos los métodos de la clase a heredar.
3. Polimorfismo
Capacidad de un objeto de pertenecer a más de una clase y de una función de ser aplicada sobre parámetros de distintas clases.
Coacción o coerción
Operación semántica por la cual se convierte un argumento al tipo esperado por una función para evitar que se produzca un error de tipos.
public class Coaccion { public static double multiplicaPorDos(double i) { return i*2; } public static void main(String[] args) { System.out.println(Coaccion.multiplicaPorDos(5.5)); // Imprime 11.0 System.out.println(Coaccion.multiplicaPorDos(5.0)); // Imprime 10.0
System.out.println(Coaccion.multiplicaPorDos(5)); // Imprime 10.0 }}
Sobrecarga y sobrescritura
Capacidad de un objeto de pertenecer a más de una clase y de una función de ser aplicada sobre parámetros de distintas clases.
public class Caja { private int lado; // Lado de la caja private int valor; // Valor interno // Constructores public Caja() { this(0, 10); } public Caja(int valor) { this(valor, 10); } public Caja(Caja c) { this(c.valor, c.lado); } public Caja(int valor, int lado) { this.valor = valor; this.lado = lado; }}
public class Sobrecarga { public void metodoX (String s) { System.out.println("Cadena " + s ); } public void metodoX (int i) { System.out.println("Entero " + i); } public void metodoX (int i, int j) { System.out.println("Enteros " + i + " y " + j); } public void metodoX (int i, String c) { System.out.println("Entero " + i + " y cadena " + c); }}
Debemos diferenciar la sobrecarga de la sobrescritura:
class Animal { public void display() { System.out.println("I am an animal"); }}class Dog extends Animal { public void display() { System.out.println("I am a dog"); }}class Main { public static void main(String[] args) { Dog dog = new Dog(); dog.display(); // I am a dog }}
Polimorfismo de inclusión
Polimorfismo que ocurre a través de las relaciones de herencia y mediante el cual una instancia de una subclase es también un instancia de sus superclases. Es el polimorfismo típico de la POO.
public static void metodo(Animal a) { ... }// ...
Animal a1 = new Perro(); // Podemos asignar un Perro a un AnimalAnimal a2 = new Gato(); // Podemos asignar un Gato a un AnimalPerro p1 = new Perro();// ...
metodo(p1); // Podemos pasar por parámetro un Perro si se pide un Animal// ...
Perro p2 = new Animal(); // Error de compilación un Animal no es un Perro
Utilidades del polimorfismo de inclusión:
- Métodos de inclusión: En Java y otros lenguajes Orientados a Objetos el polimorfismo de inclusión es utilizado para creación de métodos genéricos.
- En Java cualquier objeto es un “hijo” de la superclase
Object
, con lo cual hereda sus métodos.
- En Java cualquier objeto es un “hijo” de la superclase
- Colecciones genéricas: Podemos crear colecciones genéricas para cualquier tipo de objeto.
- Como
ArrayList
, que permitan crear listas de cualquier tipo.
- Como
- Clase envoltorio: Cuando se necesite que un tipo primitivo (Ej:
int
) se comporte como un objeto se utilizan clases envoltorio para guardar los valores.- Esto se hace de manera automática en un proceso llamado autoboxing y autounboxing.
Polimorfismo Parámetro (Genericidad)
Consiste en que una clase tiene uno, o varios, parámetros genéricos definidos. En la instanciación de dicha clase se especificará qué valores concretos tienen dichos parámetros genéricos.
public class CajaGenerica<T> { private T valor; public void setValor (T valor) { this.valor = valor; } public T getValor () { return valor; } public CajaGenerica(T valor) { this.valor = valor; }}
Esto nos permite tener una instancia de un tipo específico de una clase genérica. Si tenemos una lista de perros, será SOLO de perros.
public static void main(String[] args) { Perro p1 = new Perro("Snoopy"); CajaGenerica<Perro> c1 = new CajaGenerica<Perro>(p1);}
Hasta que no es declarado de T
lo único que sabemos de el es que es un Object
. La forma de limitar los objetos aceptados a una clase es con extends
.
public class CajaNumerica<T extends Number> { /* ... */ }
Esto no siempre será lo optimo. Ya que habrá métodos en los que queramos que un T
sea diferente a otro T
. Para esto usamos los comodines: ?
.
Si, además, queremos limitarlo a una clase concreta pero que puedan ser clases diferentes entre si:
public class CajaNumerica<T extends Number> {4 collapsed lines
private T valor; public void setValor(T valor) { this.valor = valor; } public T getValor() { return valor; } public CajaNumerica(T valor) { this.valor = valor; } boolean absEqual(CajaNumerica<? extends Number> ob) { // Comodín if(Math.abs(valor.doubleValue()) == Math.abs(ob.valor.doubleValue())) return true; else return false; } public static void main(String[] args) { CajaNumerica<Integer> n1 = new CajaNumerica<>(5); // Caja de Enteros CajaNumerica<Double> n2 = new CajaNumerica<>(5.0); // Caja de Dobles n1.absEqual(n2); // ¡¡ FUNCIONA !! }}
4. Tipificación
Un tipo es una caracterización precisa de las propiedades estructurales y de comportamiento que comparten una serie de entidades. Java tiene un tipado estático y fuerte.
class Animal { /* El metodo "ladra" no est´a definido en Animal */ }class Perro extends Animal { public void ladra() { System.out.println("Guau"); } public static void main(String[] args) { Animal unAnimal = new Perro();
unAnimal.ladra();
Perro unPerro = new Perro();
unPerro.ladra(); }}
Código diagrama
---title: Representación gráfica---flowchart TB subgraph Animal subgraph Perro subgraph Object end end end subgraph 1[Perro] subgraph 2[Animal] subgraph 3[Object] end end end Compilador[Compilador: ¿ladras?] Compilador -. NO .-> Animal Compilador -. SI .-> 1
Esto es conocido como el principio del tipado del pato (Duck Typing): La comprobación de tipos se encarga de comprobar que el objeto en cuestión tiene los aspectos deseados (camina, nada, grazna) y no de qué tipo de objeto se trata.
5. Ligadura Dinámica
Proceso que se encarga de ligar o relacionar la llamada a un método (o mensaje) con el código que se ejecuta finalmente.
abstract class Animal { public void hazRuido() { System.out.println("No hago ruido"); }}class Perro extends Animal { public void hazRuido() { ladra(); } public void ladra() { System.out.println("Guau"); }}class Pez extends Animal { /* no redefine hazRuido() */ }// ...Animal a1 = new Perro();Animal a2 = new Pez();a1.hazRuido();a2.hazRuido();// ...
GuauNo hago ruido
class Padre { public static void metodoEstatico() { System.out.println("Padre");}}class Hijo extends Padre { public static void metodoEstatico() { System.out.println("Hijo"); }}class Estaticos { public static void main(String[] args) { Padre p = new Hijo(); p.metodoEstatico(); }}
.Padre
Para evitar estos podemos usar:
-
Clases Finales: Las clases finales limitan la posible extensibilidad del código y deben por lo general evitarse salvo situaciones puntuales.
-
Métodos Finales: Los métodos finales también limitan algo esencial en la orientación a objetos como es la sobrescritura de métodos.