W3docs

Abstracción en Java

Oculta los detalles de implementación tras tipos abstractos en Java usando clases abstractas e interfaces.

La abstracción es el cuarto pilar de la POO: describe qué hace algo sin comprometerse con el cómo. Declaras las operaciones que admite un tipo, dejas la implementación a las clases concretas y escribes el resto de tu programa contra el tipo abstracto. Este capítulo es la visión conceptual; los dos mecanismos de Java para ello, abstract class e interface, tienen cada uno su propio capítulo dedicado.

Las dos preguntas

Toda declaración de tipo en Java responde a dos preguntas:

  1. ¿Qué pueden hacer los llamadores con valores de este tipo? (su API)
  2. ¿Cómo se implementa cada una de esas operaciones? (su cuerpo)

Una clase concreta responde a ambas. Un tipo abstracto responde solo a la primera y deja la segunda a los subtipos:

public interface Shape {
  double area();         // what — every Shape has an area
}

public class Circle implements Shape {
  double r;
  public Circle(double r) { this.r = r; }
  public double area() { return Math.PI * r * r; }   // how
}
public class Square implements Shape {
  double side;
  public Square(double side) { this.side = side; }
  public double area() { return side * side; }
}

Shape dice "toda figura tiene un área." Circle y Square dicen cómo calcularla. El código que recibe un Shape no necesita saber cuál es:

double sumAreas(List<Shape> shapes) {
  double sum = 0;
  for (Shape s : shapes) sum += s.area();
  return sum;
}

Esta función está cerrada sobre la abstracción. Funciona con Circle y Square hoy; con Triangle mañana; con Polygon dentro de seis meses. Ninguno de los tipos nuevos requiere ningún cambio en sumAreas.

Los dos mecanismos de Java

MecanismoQué proporcionaCuándo usarlo
abstract classUna clase parcial — algunos métodos abstractos, otros con cuerpo, además de campos y constructoresCuando los subtipos compartirán estado e infraestructura de código
interfaceUn contrato puro (o casi puro) — métodos que las clases implementadoras deben proveer; sin estado de instanciaCuando los subtipos solo necesitan acordar un conjunto de operaciones y pueden no tener nada más en común

Una clase extiende una única clase abstracta. Una clase puede implementar muchas interfaces. Esa asimetría orienta muchos diseños: si te encuentras queriendo "herencia múltiple," las interfaces suelen ser la respuesta.

Clases abstractas — implementación parcial

abstract en una clase significa "no puedes instanciar esto directamente — solo las subclases." abstract en un método significa "sin cuerpo aquí; cada subclase concreta debe proporcionar uno":

public abstract class Shape {
  public abstract double area();      // every Shape must define this

  // a concrete method, shared across all shapes
  public final String describe() {
    return getClass().getSimpleName() + " area=" + area();
  }
}

new Shape() es un error de compilación. new Circle() funciona. Dentro de describe, la llamada area() se despacha a la implementación de la subclase real — el mismo mecanismo de polimorfismo que cualquier método sobreescrito.

Usa una clase abstracta cuando los subtipos realmente comparten código. Si te encuentras escribiendo el mismo auxiliar en tres subclases, esa es una señal para moverlo al padre.

Interfaces — el contrato

Una interface declara operaciones y deja la implementación completamente a quien la implemente:

public interface Comparable<T> {
  int compareTo(T other);
}

public class Money implements Comparable<Money> {
  private final long cents;
  public int compareTo(Money other) {
    return Long.compare(this.cents, other.cents);
  }
}

Ahora Money funciona en cualquier lugar donde se espere un ComparableCollections.sort(...), TreeMap, Arrays.sort(...), tus propios algoritmos genéricos. La biblioteca estándar y tu código coinciden en Comparable como abstracción compartida; ninguno de los dos conoce al otro.

La gran mayoría de las interfaces estándar de Java (List, Map, Iterable, Runnable, Function, Comparator, AutoCloseable) funcionan así: un contrato pequeño y enfocado en el que enchufan muchas clases concretas.

La abstracción como palanca de diseño

La parte mecánica de la abstracción — la palabra clave abstract, la declaración interface — es pequeña. La parte difícil es elegir qué abstracciones definir. Tres patrones que aparecen una y otra vez:

  • Strategy. Define una interface para "el algoritmo." Distintas implementaciones intercambian el algoritmo sin cambiar el código que lo usa. Comparator es el ejemplo clásico.
  • Template method. Una clase abstracta implementa el flujo general, con métodos abstractos en los puntos de variación. Las subclases completan los pasos específicos. El método service de HttpServlet es un ejemplo famoso.
  • Plugin / punto de extensión. Una biblioteca publica una interface; el código del usuario la implementa; la biblioteca llama de vuelta a ella. Las API de Servlet, los drivers JDBC, el BeanPostProcessor de Spring.

En todos los casos, la ganancia es la misma: el código que depende de la abstracción está cerrado frente a cambios en las implementaciones, y abierto a que se añadan implementaciones adicionales más adelante.

Encapsulamiento vs abstracción

Estos dos son primos cercanos y a menudo se confunden.

  • El encapsulamiento oculta la implementación de una clase específica (campos privados, métodos controlados). Es una preocupación interna a la clase.
  • La abstracción oculta qué clase estás usando detrás de un contrato compartido. Es una preocupación externa a la clase.

Una clase con campos private y una API pública ordenada está encapsulada, pero todavía no está abstraída — los llamadores siguen dependiendo de esa clase específica. Reemplaza el tipo en el límite de la API con una interface, y los llamadores dependen del contrato en su lugar. Ahora puedes intercambiar implementaciones.

Para verlos trabajar juntos, consulta el capítulo de encapsulamiento: el encapsulamiento bloquea una sola clase, la abstracción permite a los llamadores ignorar qué clase tienen.

Errores comunes

Algunas trampas atrapan a los recién llegados a la abstracción:

  • Intentar instanciar un tipo abstracto. new Shape() es un error de compilación cuando Shape es abstract o una interface. Instancias un subtipo concreto (new Circle(2)) y lo asignas a la referencia abstracta.
  • Abstraer demasiado pronto. Una interface con exactamente una implementación, escrita "por si necesitamos otra después," suele ser peso muerto. Añade la abstracción cuando aparezca la segunda implementación, o cuando realmente necesites desacoplar dos módulos. La abstracción prematura añade indirección sin ganar flexibilidad.
  • Filtrar el tipo concreto. Declarar un campo o parámetro como ArrayList en lugar de List, o devolver HashMap en lugar de Map, ata a los llamadores a esa clase específica y deshace la abstracción. Prefiere el tipo más abstracto que aún exprese lo que necesitas.
  • Confundir "sin cuerpo" con "no hace nada." Un método abstracto no tiene cuerpo porque las subclases deben proporcionar uno. Un método concreto con un cuerpo vacío es un método real que no hace nada — un contrato muy diferente.

Un ejemplo resuelto

Ejecuta el programa a continuación. Ejercita ambos mecanismos: una clase abstracta Shape con código compartido describe, y una interface pura Greeter. La salida esperada es:

Circle area=12.566370614359172
Square area=9.0
total = 21.57

Dear Alice,
hey Alice!

Observa que totalArea y el bucle de saludos nunca nombran Circle, Square, FormalGreeter ni CasualGreeter — solo hablan con las abstracciones Shape y Greeter.

java— editable, runs on the server

Qué sigue

El siguiente capítulo trata la mecánica concreta de las clases abstractas — métodos abstractos, qué pueden heredar las subclases y cuándo elegirlas sobre las interfaces.

Práctica

Práctica
¿Cuál captura mejor la diferencia entre encapsulamiento y abstracción?
¿Cuál captura mejor la diferencia entre encapsulamiento y abstracción?
Práctica
¿Cuándo deberías elegir una interface en lugar de una clase abstracta?
¿Cuándo deberías elegir una interface en lugar de una clase abstracta?
Was this page helpful?