Saltar al contenido principal

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 extends cuando 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.