Diferencias
Muestra las diferencias entre dos versiones de la página.
| Ambos lados, revisión anterior Revisión previa Próxima revisión | Revisión previa | ||
|
clase:daw:prog:2eval:clases_abstractas_interfaces [2022/07/20 10:24] cesguiro [Utilidad de las interfaces] |
— (actual) | ||
|---|---|---|---|
| Línea 1: | Línea 1: | ||
| - | ====== 11 - Clases y Métodos abstractos. Interfaces ====== | ||
| - | ===== Métodos abstractos ===== | ||
| - | |||
| - | Vamos a seguir con nuestro ejemplo de los vehículos con herencia. Tenemos la clase // | ||
| - | |||
| - | <sxh java; highlight: | ||
| - | public class Vehiculo { | ||
| - | private String marca, modelo; | ||
| - | private int velocidad; | ||
| - | private float[] presionRuedas; | ||
| - | private String matricula; | ||
| - | | ||
| - | ... | ||
| - | </ | ||
| - | |||
| - | Supongamos que el proceso de matriculación es diferente para los distintos vehículos (las motos de menos de 250cc no pagan impuesto de matriculación, | ||
| - | |||
| - | Existe una forma de obligar a todas las clases descendientes a implementar un método: los **métodos abstractos**. Un método abstracto es aquel que tiene solo la definición de la cabecera, pero no su implementación. Por ejemplo, podemos crear el método abstracto // | ||
| - | |||
| - | <sxh java; highlight: | ||
| - | public class Vehiculo { | ||
| - | private String marca, modelo; | ||
| - | private int velocidad; | ||
| - | private float[] presionRuedas; | ||
| - | private String matricula; | ||
| - | | ||
| - | ... | ||
| - | | ||
| - | public abstract void matricular(String matricula); | ||
| - | </ | ||
| - | |||
| - | Fíjate que el método // | ||
| - | |||
| - | Lo primero que pasa al crear un método abstracto es que Java nos da un error. ¿Por qué? Simplemente, | ||
| - | |||
| - | <sxh java; highlight: | ||
| - | public abstract class Vehiculo { | ||
| - | private String marca, modelo; | ||
| - | private int velocidad; | ||
| - | private float[] presionRuedas; | ||
| - | private String matricula; | ||
| - | | ||
| - | ... | ||
| - | | ||
| - | public abstract void matricular(String matricula); | ||
| - | | ||
| - | ... | ||
| - | </ | ||
| - | |||
| - | Vamos a aprovechar y a crear otro método que nos será útil más adelante. Será de tipo // | ||
| - | |||
| - | <sxh java> | ||
| - | public abstract class Vehiculo { | ||
| - | private String marca, modelo; | ||
| - | private int velocidad; | ||
| - | private float[] presionRuedas; | ||
| - | private String matricula; | ||
| - | |||
| - | ... | ||
| - | | ||
| - | public abstract void matricular(String matricula); | ||
| - | |||
| - | protected void setMatricula(String matricula){ | ||
| - | this.matricula = matricula; | ||
| - | } | ||
| - | |||
| - | ... | ||
| - | </ | ||
| - | |||
| - | El método // | ||
| - | |||
| - | Al definir la clase como abstracta, vemos que nos desaparece el error anterior, pero ahora nos muestra errores en las clases //Coche// y //Moto//. Esos errores nos están indicando que ambas clases tienen que implementar **obligatoriamente** los métodos abstractos de sus clases antecesoras. Si creamos el método en la clase //Coche// vemos como desaparece el error: | ||
| - | |||
| - | <sxh java> | ||
| - | public class Coche extends Vehiculo{ | ||
| - | private boolean ruedaRepuesto; | ||
| - | |||
| - | ... | ||
| - | | ||
| - | @Override | ||
| - | public void matricular(String matricula) { | ||
| - | this.setMatricula(matricula); | ||
| - | System.out.println(" | ||
| - | } | ||
| - | </ | ||
| - | |||
| - | Lo único que hace el método es mostrar la frase "Pagar impuesto de matriculación" | ||
| - | |||
| - | <sxh java> | ||
| - | public class Moto extends Vehiculo{ | ||
| - | private int cilindrada; | ||
| - | |||
| - | public Moto(String marca, String modelo, int velocidad, float[] presionRuedas, | ||
| - | super(marca, | ||
| - | this.cilindrada = cilindrada; | ||
| - | } | ||
| - | |||
| - | @Override | ||
| - | public void matricular(String matricula) { | ||
| - | if(this.cilindrada< | ||
| - | System.out.println(" | ||
| - | } else { | ||
| - | System.out.println(" | ||
| - | } | ||
| - | } | ||
| - | | ||
| - | } | ||
| - | </ | ||
| - | |||
| - | Vamos a crear un coche y un par de motos en nuestra clase principal para comprobar que todo funciona: | ||
| - | |||
| - | <sxh java> | ||
| - | public class Main { | ||
| - | | ||
| - | public static void main(String[] args) { | ||
| - | Coche coche1 = new Coche( | ||
| - | " | ||
| - | " | ||
| - | 0, | ||
| - | new float[] {2.4f, 2.4f, 2.4f, 2.4f}, | ||
| - | true | ||
| - | ); | ||
| - | Moto moto1 = new Moto( | ||
| - | " | ||
| - | "NC 750", | ||
| - | 0, | ||
| - | new float[] {2.5f, 2.5f}, | ||
| - | 750 | ||
| - | ); | ||
| - | Moto moto2 = new Moto( | ||
| - | " | ||
| - | " | ||
| - | 0, | ||
| - | new float[] {2.2f, 2.2f}, | ||
| - | 125 | ||
| - | ); | ||
| - | |||
| - | coche1.matricular(" | ||
| - | moto1.matricular(" | ||
| - | moto2.matricular(" | ||
| - | } | ||
| - | } | ||
| - | </ | ||
| - | |||
| - | <sxh base> | ||
| - | Pagar impuesto de matriculación | ||
| - | Pagar impuesto matriculación | ||
| - | No hace falta pagar impuesto matriculación | ||
| - | </ | ||
| - | |||
| - | ==== Propiedades de los métodos abstractos ==== | ||
| - | |||
| - | * Podemos declarar clases abstractas sin necesidad de tener algún método abstracto. Aunque no es lo habitual, puede ser útil si queremos definir métodos comunes de las clases descendientes pero no queremos que se puedan crear objetos de dicha clase. | ||
| - | * Un método abstracto solo se puede definir como // | ||
| - | * No podemos reducir la visibilidad de un método abstracto en las clases hijas. Es decir, si en la clase padre hemos declarado el método abstracto como //public//, en las clases hijas que implementen ese método no podemos definirlas con una accesibilidad más restrictiva (// | ||
| - | |||
| - | ===== Interfaces ===== | ||
| - | |||
| - | Podríamos definir las **interfaces** como clases donde todos sus métodos son abstractos (no todos, como veremos a continuación). En realidad, tienen algunas características más: | ||
| - | |||
| - | * Las // | ||
| - | * Una // | ||
| - | * Podemos definir atributos en la // | ||
| - | * Una clase puede heredar (implementar) de una o varias // | ||
| - | |||
| - | Para declarar una // | ||
| - | |||
| - | <sxh java> | ||
| - | public interface nombreInterface { | ||
| - | |||
| - | ... | ||
| - | |||
| - | } | ||
| - | </ | ||
| - | |||
| - | Si queremos que una clase // | ||
| - | |||
| - | <sxh java> | ||
| - | public class miClase implements nombreInterface { | ||
| - | |||
| - | ... | ||
| - | |||
| - | } | ||
| - | </ | ||
| - | |||
| - | ==== Utilidad de las interfaces ==== | ||
| - | |||
| - | Aunque en un principio pueda parecer que las // | ||
| - | |||
| - | Por ejemplo, supongamos que estamos haciendo un programa que permite leer documentos //pdf// donde tenemos almacenados nuestros clientes. | ||
| - | |||
| - | <sxh java> | ||
| - | public class Cliente { | ||
| - | |||
| - | int id; | ||
| - | String nombre, CIF; | ||
| - | | ||
| - | ... | ||
| - | } | ||
| - | </ | ||
| - | |||
| - | <sxh java> | ||
| - | public class DocumentoPdf { | ||
| - | | ||
| - | String path; | ||
| - | | ||
| - | ... | ||
| - | |||
| - | public Cliente getClienteFromPdf(int id){ | ||
| - | Cliente cliente = new Cliente(); | ||
| - | ... | ||
| - | return cliente; | ||
| - | } | ||
| - | | ||
| - | ... | ||
| - | } | ||
| - | </ | ||
| - | |||
| - | En nuestra clase principal, usamos esas clases para buscar el cliente con //id// = 2: | ||
| - | |||
| - | <sxh java> | ||
| - | public class Main { | ||
| - | | ||
| - | public static void main(String[] args) { | ||
| - | DocumentoPdf documento = new DocumentoPdf(); | ||
| - | Cliente cliente = documento.getClienteFromPdf(2); | ||
| - | } | ||
| - | } | ||
| - | </ | ||
| - | |||
| - | La aplicación funciona como toca y nos busca clientes en los //pdf// sin ningún problema. Al cabo del tiempo, sustituimos los documentos //pdf// por //json//. Tenemos que crear una nueva clase (// | ||
| - | |||
| - | <sxh java> | ||
| - | public class DocumentoJson { | ||
| - | | ||
| - | String path; | ||
| - | |||
| - | public Cliente getClienteFromJson(int id){ | ||
| - | Cliente cliente = new Cliente(); | ||
| - | ... | ||
| - | return cliente; | ||
| - | } | ||
| - | } | ||
| - | </ | ||
| - | |||
| - | Además, tenemos que sustituir la llamada al método desde todas las clases donde lo hemos utilizado (en nuestro caso, solo está en nuestra clase //Main//): | ||
| - | |||
| - | <sxh java> | ||
| - | public class Main { | ||
| - | | ||
| - | public static void main(String[] args) { | ||
| - | DocumentoJson documento = new DocumentoJson(); | ||
| - | Cliente cliente = documento.getClienteFromJson(2); | ||
| - | } | ||
| - | } | ||
| - | </ | ||
| - | |||
| - | Imagínate si tuviésemos varias referencias al método. Nos tocaría buscar donde lo hemos utilizado y sustituirlos. Si después cambiamos a otro tipo de documentos o base de datos o cualquier otro tipo de persistencia, | ||
| - | |||
| - | Vamos a hacer lo mismo, pero usando // | ||
| - | |||
| - | <sxh java> | ||
| - | public interface Documento { | ||
| - | | ||
| - | public Cliente getCliente(int id); | ||
| - | } | ||
| - | </ | ||
| - | |||
| - | En principio (igual que antes) leemos los clientes de documentos //pdf//. Para eso, creamos la clase // | ||
| - | |||
| - | <sxh java> | ||
| - | public class DocumentoPdf implements Documento{ | ||
| - | | ||
| - | String path; | ||
| - | |||
| - | public Cliente getCliente(int id){ | ||
| - | Cliente cliente = new Cliente(); | ||
| - | //... | ||
| - | return cliente; | ||
| - | } | ||
| - | } | ||
| - | </ | ||
| - | |||
| - | En nuestra clase principal, usamos esas clases y métodos: | ||
| - | |||
| - | <sxh java> | ||
| - | public class Main { | ||
| - | | ||
| - | public static void main(String[] args) { | ||
| - | Documento documento = new DocumentoPdf(); | ||
| - | |||
| - | Cliente cliente = documento.getCliente(2); | ||
| - | } | ||
| - | } | ||
| - | </ | ||
| - | |||
| - | Date cuenta que estamos usando // | ||
| - | |||
| - | Si cambiamos y queremos usar documentos Json, creamos la nueva clase implementando de nuevo el // | ||
| - | |||
| - | <sxh java> | ||
| - | public class DocumentoJson implements Documento{ | ||
| - | | ||
| - | String path; | ||
| - | |||
| - | public Cliente getCliente(int id){ | ||
| - | Cliente cliente = new Cliente(); | ||
| - | //... | ||
| - | return cliente; | ||
| - | } | ||
| - | } | ||
| - | </ | ||
| - | |||
| - | Ahora, lo único que tenemos que cambiar es el tipo de documento en nuestra clase principal, pero el método no hace falta cambiarlo: | ||
| - | |||
| - | <sxh java; highlight: | ||
| - | public class Main { | ||
| - | | ||
| - | public static void main(String[] args) { | ||
| - | Documento documento = new DocumentoJson(); | ||
| - | |||
| - | Cliente cliente = documento.getCliente(2); | ||
| - | } | ||
| - | } | ||
| - | </ | ||
| - | |||
| - | Con este enfoque, desacoplamos las clases entre sí, permitiendo hacer cambios en unas sin que afecten a las otras. | ||
| - | |||
| - | <note important> | ||
| - | |||
| - | <note important> | ||