Interfaces
Una clase abstracta puede compartir estado e implementación. A veces solo necesitamos expresar una capacidad común: «se puede enviar», aunque las clases no pertenezcan a la misma familia.
Un contrato
interface Enviable {
String enviar();
}
Una interfaz define operaciones que sus implementaciones se comprometen a ofrecer. No decide cómo se realizan.
class Email implements Enviable {
private String destinatario;
public Email(String destinatario) { this.destinatario = destinatario; }
@Override public String enviar() { return "Email enviado a " + destinatario; }
}
class Sms implements Enviable {
private String telefono;
public Sms(String telefono) { this.telefono = telefono; }
@Override public String enviar() { return "SMS enviado a " + telefono; }
}
implements no aporta estado ni constructor. Obliga a cumplir el contrato, y @Override permite que el compilador compruebe la firma.
Polimorfismo mediante la interfaz
interface Enviable { String enviar(); }
class Email implements Enviable {
private String destinatario;
public Email(String destinatario) { this.destinatario = destinatario; }
@Override public String enviar() { return "Email enviado a " + destinatario; }
}
class Sms implements Enviable {
private String telefono;
public Sms(String telefono) { this.telefono = telefono; }
@Override public String enviar() { return "SMS enviado a " + telefono; }
}
public class EnviosDemo {
public static void main(String[] args) {
Enviable[] envios = { new Email("ana@example.com"), new Sms("600123123") };
for (Enviable envio : envios) {
System.out.println(envio.enviar());
}
}
}
El recorrido depende del contrato, no de clases concretas. Cada objeto decide cómo cumplirlo.
Varias capacidades
Java no permite extender varias clases, pero una clase puede implementar varias interfaces:
interface Imprimible { String imprimir(); }
interface Exportable { String exportar(); }
class Informe implements Imprimible, Exportable {
@Override public String imprimir() { return "Informe impreso"; }
@Override public String exportar() { return "Informe exportado"; }
}
Esto combina capacidades sin crear una jerarquía artificial.
Clase abstracta o interfaz
- Elige una clase abstracta cuando las hijas comparten estado, constructor o implementación base y existe una relación «es un» clara.
- Elige una interfaz para definir una capacidad que clases diferentes pueden cumplir.
- Una clase puede extender una sola clase e implementar varias interfaces.
No es una competición: una clase puede extender una base e implementar un contrato si ambas decisiones tienen sentido.
Métodos default
Una interfaz puede ofrecer un comportamiento opcional compartido:
interface Enviable {
String enviar();
default String estado() { return "Preparado para enviar"; }
}
Úsalo con moderación. El valor principal de la interfaz sigue siendo el contrato; no debe convertirse en un almacén de lógica.
Errores habituales
- Escribir
extendscuando una clase cumple una interfaz. - Olvidar implementar una operación y mantener concreta la clase.
- Añadir getters y setters a la interfaz aunque no formen parte de la capacidad.
- Elegir interfaz cuando se necesita compartir estado.
- Depender de la clase concreta después de declarar una referencia por contrato.
Practica: medios de pago
Enunciado y criterios
Define Pagable con pagar(double importe). Implementa tarjeta y efectivo. Guarda ambas implementaciones en un array Pagable[] y procesa el mismo importe. No compartas estado artificialmente.
Solución
interface Pagable {
String pagar(double importe);
}
class PagoTarjeta implements Pagable {
@Override public String pagar(double importe) { return "Tarjeta: " + importe + " euros"; }
}
class PagoEfectivo implements Pagable {
@Override public String pagar(double importe) { return "Efectivo: " + importe + " euros"; }
}
public class PagosDemo {
public static void main(String[] args) {
Pagable[] medios = { new PagoTarjeta(), new PagoEfectivo() };
for (Pagable medio : medios) {
System.out.println(medio.pagar(25.0));
}
}
}
Tarjeta y efectivo no necesitan un padre con datos comunes. Lo que importa es que ambos aceptan la misma operación. En la próxima unidad separaremos estas piezas en archivos y packages coherentes.