← dev-notes
POO · PARTE 1 DE 2

Los 4 Pilares de la
POO

Encapsulamiento, herencia, polimorfismo y abstracción — herramientas y trampas de cada uno

POO · P1
🔒 Encapsulamiento 🌳 Herencia 🔀 Polimorfismo 🎭 Abstracción
01 Encapsulamiento
DATOS + COMPORTAMIENTO · ACCESO CONTROLADO
PILAR 01 DE 04
🔒
Encapsulamiento
Agrupar datos y el comportamiento que los opera en una misma unidad (clase), y restringir el acceso directo a los datos internos. El objeto controla cómo se lee y modifica su estado — nadie más puede hacerlo directamente.
private / protected getters / setters con lógica
Exponer getters y setters para cada campo es encapsulamiento roto. Un setter sin validación es un campo público con pasos extra.
SRP y OCP. Al encapsular, defines una frontera clara de responsabilidad. El estado solo cambia a través de métodos que imponen las reglas del negocio — exactamente lo que DDD llama invariante.
sin encapsulamiento❌ ROTO
class BankAccount {
  public double balance;
  public String owner;
}

// Cualquiera puede hacer:
account.balance = -5000;
account.owner = null;
// No hay reglas de negocio posibles
// El objeto no protege su integridad
// → cualquier bug es un bug de estado
con encapsulamiento✅ CORRECTO
class BankAccount {
  private double balance;
  private final String owner;

  public void deposit(double amount) {
    if (amount <= 0)
      throw new IllegalArgumentException(
        "Amount must be positive");
    this.balance += amount;
  }
  // balance solo cambia por reglas del negocio
  // owner es inmutable desde la creación
  public double getBalance() { return balance; }
}
02 Herencia
RELACIÓN IS-A · REUTILIZACIÓN DE COMPORTAMIENTO
PILAR 02 DE 04
🌳
Herencia
Una clase hija adquiere atributos y métodos de su clase padre. Válida cuando existe una relación semántica "ES-UN" (IS-A) real, no cuando se usa solo para reusar código. Es la raíz de la mayor parte del mal diseño OO.
extends IS-A real abstract class
Heredar para reutilizar código cuando no existe IS-A semántico. Stack extends ArrayList es el ejemplo clásico: Stack no ES un ArrayList. Resultado: la subclase hereda métodos que no debería tener (LSP roto).
LSP directamente. Toda subclase debe poder sustituir a su padre sin romper el comportamiento esperado. Si no es así, la herencia no es correcta — usa composición.
herencia inapropiada❌ LSP ROTO
// Stack NO ES un ArrayList
// Solo hereda para reutilizar lógica
class Stack<T> extends ArrayList<T> {
  public T pop() { /* ... */ }
  public void push(T item) { /* ... */ }
}

// Ahora Stack tiene add(), remove(),
// set(), get(index)… que NO son Stack.
// Cualquier código que use ArrayList
// puede romper Stack → LSP violado.
herencia IS-A real✅ CORRECTO
abstract class Shape {
  abstract double area();
  void describe() {
    System.out.println("Area: " + area());
  }
}

// Circle ES-UN Shape → IS-A real
class Circle extends Shape {
  private final double radius;
  double area() {
    return Math.PI * radius * radius;
  }
}
// Circle puede sustituir a Shape siempre
03 Polimorfismo
MISMO MENSAJE · COMPORTAMIENTO DIFERENTE
PILAR 03 DE 04
🔀
Polimorfismo
La capacidad de un mensaje (llamada a método) de producir comportamientos distintos según el tipo concreto del objeto receptor. El emisor no sabe — ni necesita saber — qué tipo concreto recibe. Es el mecanismo que hace que "Replace Conditional with Polymorphism" funcione.
@Override interface / abstract dynamic dispatch
Confundir polimorfismo con if/instanceof. Si tienes if (obj instanceof Circle) para decidir comportamiento, estás negando el polimorfismo — el compilador ya lo puede resolver.
OCP directamente. El código que usa una interfaz polimórfica está cerrado a modificación cuando aparece un nuevo tipo — solo se añade una nueva implementación. También habilita DIP: depender de abstracciones, no de concretos.
sin polimorfismo❌ OCP ROTO
void drawShape(Object shape) {
  if (shape instanceof Circle) {
    ((Circle) shape).drawCircle(canvas);
  } else if (shape instanceof Rectangle) {
    ((Rectangle) shape).drawRect(canvas);
  } else if (shape instanceof Triangle) {
    ((Triangle) shape).drawTri(canvas);
  }
  // nueva figura → modificar aquí siempre
}
con polimorfismo✅ OCP CORRECTO
interface Drawable {
  void draw(Canvas canvas);
}
class Circle implements Drawable {
  public void draw(Canvas c) { /* círculo */ }
}
class Triangle implements Drawable {
  public void draw(Canvas c) { /* triángulo */ }
}

void drawShape(Drawable shape) {
  shape.draw(canvas); // dispatch automático
}
// nueva figura = nueva clase, nada más
04 Abstracción
OCULTAR COMPLEJIDAD · EXPONER INTENCIÓN
PILAR 04 DE 04
🎭
Abstracción
Exponer solo lo relevante para el usuario del objeto, ocultando los detalles de implementación. El contrato público es la abstracción; la implementación es el detalle. Es lo que permite cambiar la implementación sin afectar a quien usa la interfaz.
interface abstract class API surface
Filtrar detalles de implementación en la interfaz pública: tipos concretos como parámetros o retornos, excepciones propias del framework, métodos que solo tienen sentido para una implementación concreta.
DIP completo. Cuando dependes de una abstracción (interfaz), el módulo de alto nivel no sabe nada de la implementación. Es la base de la arquitectura Hexagonal: el dominio depende de puertos (abstracciones), nunca de adaptadores (implementaciones).
abstracción rota — implementación expuesta❌ DIP ROTO
// El servicio expone su implementación
class NotificationService {
  public JavaMailSenderImpl mailSender;
  public Session smtpSession;

  public void sendRawMime(MimeMessage msg) {
    /* detalles SMTP */
  }
}
// el cliente necesita saber de SMTP,
// MimeMessage y JavaMail → acoplado
abstracción correcta — contrato limpio✅ DIP CORRECTO
// Abstracción: solo expone intención
interface NotificationPort {
  void notify(String to, String subject,
               String body);
}

// Implementación: oculta los detalles
class SmtpNotification
    implements NotificationPort {
  public void notify(...) {
    // SMTP, MimeMessage, etc. aquí
    // el cliente no sabe nada de esto
  }
}
Los 4 pilares — visión de conjunto
Pilar
Qué garantiza
Trampa frecuente
SOLID relacionado
Encapsulamiento
Integridad del estado interno del objeto
Getters/setters sin lógica = campo público
SRP · OCP
Herencia
Reutilización cuando IS-A es real
Heredar para reusar código sin IS-A
LSP
Polimorfismo
Extensibilidad sin modificar código existente
Usar instanceof en lugar de dispatch
OCP · DIP
Abstracción
Desacoplamiento de implementaciones concretas
Filtrar tipos concretos en la interfaz pública
DIP · ISP
🔗
La relación con SOLID — por qué estudiar POO antes
SOLID no es una lista de reglas arbitrarias — es la respuesta a los problemas que genera mal uso de los 4 pilares. SRP corrige el encapsulamiento roto. LSP define cuándo la herencia es correcta. OCP y DIP explotan el polimorfismo y la abstracción correctamente. Sin entender los pilares, SOLID parece dogma. Con ellos, SOLID parece obvio.