OOP Preguntas de entrevista
Las entrevistas de Programación Orientada a Objetos (POO) evalúan tu comprensión de conceptos fundamentales como encapsulamiento, herencia, polimorfismo y abstracción, así como tu capacidad para aplicar patrones de diseño y escribir código limpio y mantenible. Estas preguntas son comunes para roles de ingeniería de software en todos los niveles, desde junior hasta senior, pero especialmente para posiciones senior donde las decisiones de diseño y arquitectura importan. Los entrevistadores esperan que expliques conceptos claramente, los relaciones con ejemplos del mundo real y resuelvas problemas de codificación que demuestren los principios de POO en acción.
Lo que cubren las entrevistas de OOP
Principios Fundamentales
Preguntas sobre encapsulamiento, herencia, polimorfismo y abstracción con ejemplos y analogías del mundo real.
Patrones de Diseño
Patrones comunes como Singleton, Factory, Observer y Strategy, sus implementaciones y cuándo usarlos.
Diseño de Clases y Relaciones
Herencia vs composición, clases abstractas vs interfaces, acoplamiento/cohesión, principios SOLID.
Problemas Prácticos de Codificación
Implementación de jerarquías de clases, diseño de un estacionamiento o un sistema de biblioteca, y refactorización de código procedural a POO.
Ejemplos de preguntas de entrevista sobre OOP
- Explica los cuatro pilares de la POO y da ejemplos del mundo real.Lo que cubre una buena respuesta
- Abstracción: ocultar detalles y mostrar solo funcionalidades esenciales.
- Encapsulamiento: agrupar datos y métodos y restringir acceso.
- Herencia: crear nuevas clases basadas en clases existentes.
- Polimorfismo: tratar objetos de diferentes tipos de manera uniforme.
Ver respuesta de ejemplo
Los cuatro pilares de la POO son: abstracción, encapsulamiento, herencia y polimorfismo. La abstracción permite modelar objetos del mundo real ocultando complejidades, por ejemplo una clase 'Vehículo' expone métodos como 'acelerar' sin mostrar el motor. El encapsulamiento protege el estado interno de un objeto, como una cuenta bancaria que sólo permite modificar el saldo a través de métodos públicos. La herencia establece una relación 'es-un', por ejemplo 'Perro' hereda de 'Animal' sus atributos y comportamientos. El polimorfismo permite que objetos de distintas clases respondan al mismo mensaje de forma específica, como diferentes formas que implementan un método 'calcularArea'.
- ¿Cuál es la diferencia entre una clase abstracta y una interfaz? ¿Cuándo usarías cada una?Lo que cubre una buena respuesta
- Las clases abstractas pueden tener estado y constructores; las interfaces no (hasta Java 8).
- Una clase puede heredar de una sola clase abstracta, pero implementar múltiples interfaces.
- Usa clase abstracta cuando las subclases comparten estado o comportamiento parcial.
- Usa interfaz para definir un contrato que cualquier clase puede cumplir, independientemente de la jerarquía.
Ver respuesta de ejemplo
Una clase abstracta puede contener campos, constructores y métodos concretos, mientras que una interfaz tradicional solo define métodos abstractos (aunque desde Java 8 puede tener métodos default y static). La herencia de clases abstractas es única, pero se pueden implementar varias interfaces. Se debe usar una clase abstracta cuando las subclases comparten una implementación común o estado (por ejemplo, una clase 'Animal' con atributos como 'nombre'). Se debe usar una interfaz cuando se quiere definir un comportamiento que puede ser adoptado por clases no relacionadas (por ejemplo 'Volador' para aves y aviones). Elegir incorrectamente puede llevar a jerarquías rígidas o duplicación de código.
- Implementa un patrón Singleton en Java (o el lenguaje de tu elección) y discute la seguridad en concurrencia.Lo que cubre una buena respuesta
- El patrón Singleton asegura una única instancia de una clase.
- Implementación típica: constructor privado, método estático de acceso y campo privado.
- En entornos concurrentes, se necesita sincronización para evitar múltiples instancias.
- El doble chequeo con 'volatile' es una técnica eficiente y segura.
Ver respuesta de ejemplo
El patrón Singleton garantiza que una clase tenga solo una instancia y proporciona un punto de acceso global. La implementación más común en Java incluye un constructor privado, un campo estático privado y un método público estático para obtener la instancia. Para manejar la concurrencia, se puede usar inicialización temprana, sincronización en el método o doble chequeo con 'volatile'. El doble chequeo verifica si la instancia es nula antes de sincronizar, y dentro del bloque sincronizado se vuelve a verificar, lo que minimiza la sobrecarga. El uso de 'volatile' evita problemas de reordenamiento de instrucciones. Sin embargo, los Singletons pueden dificultar las pruebas y violar el principio de responsabilidad única.
Solución de referenciajava public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } - Diseña una jerarquía de clases para formas (Círculo, Rectángulo) con cálculo de área, demostrando polimorfismo.Lo que cubre una buena respuesta
- Clase base 'Shape' con método abstracto 'area'.
- Subclases 'Circle' y 'Rectangle' que implementan 'area'.
- Polimorfismo: tratar objetos de ambas clases como 'Shape' y llamar a 'area'.
- Ejemplo en 'main' con lista de formas.
Ver respuesta de ejemplo
Para demostrar polimorfismo, se crea una jerarquía con una clase base 'Shape' que declara un método abstracto 'area()'. Las subclases 'Circle' (con radio) y 'Rectangle' (con alto y ancho) implementan 'area()' de manera específica. Desde el punto de vista del cliente, se puede tratar cualquier forma como 'Shape' y llamar a 'area()', que ejecutará la implementación correcta según el tipo real. Esto permite escribir código genérico, por ejemplo un bucle que recorre una lista de 'Shape' y calcula áreas sin importar la forma concreta. Es una aplicación clara del polimorfismo de subtipos.
Solución de referenciajava abstract class Shape { abstract double area(); } class Circle extends Shape { private double radius; public Circle(double r) { radius = r; } @Override double area() { return Math.PI * radius * radius; } } class Rectangle extends Shape { private double width, height; public Rectangle(double w, double h) { width = w; height = h; } @Override double area() { return width * height; } } public class Main { public static void main(String[] args) { Shape[] shapes = { new Circle(5), new Rectangle(4, 6) }; for (Shape s : shapes) { System.out.println("Area: " + s.area()); } } } - ¿Cómo la herencia lleva a un acoplamiento fuerte? Prefiere composición sobre herencia – explica con un ejemplo.Lo que cubre una buena respuesta
- La herencia crea acoplamiento fuerte porque las subclases dependen de la implementación de la superclase.
- Cambios en la superclase pueden romper subclases (problema de la base frágil).
- La composión favorece la reutilización mediante delegación y acoplamiento débil.
- Ejemplo: un coche 'tiene un' motor en lugar de 'es un' vehículo con motor.
Ver respuesta de ejemplo
La herencia introduce acoplamiento fuerte porque la subclase conoce y depende de los detalles internos de la superclase. Cualquier cambio en la superclase (como añadir un método o modificar uno existente) puede afectar a todas las subclases, lo que se conoce como el 'problema de la base frágil'. La composición, en cambio, se basa en el principio 'tiene un' (has-a), donde una clase contiene instancias de otras y delega funcionalidad. Esto reduce el acoplamiento, ya que los objetos internos pueden intercambiarse fácilmente. Por ejemplo, en lugar de que 'Coche' herede de 'Motor', se debe usar composición: 'Coche' tiene un 'Motor'. Así, si se cambia la implementación del motor, el coche no se ve afectado siempre que la interfaz se mantenga.
- Escribe código para demostrar cómo implementarías un patrón strategy para calcular costos de envío.Lo que cubre una buena respuesta
- Patrón Strategy: define una familia de algoritmos y los hace intercambiables.
- Interfaz 'ShippingStrategy' con método 'calculateCost'.
- Implementaciones concretas: 'StandardShipping' y 'ExpressShipping'.
- Clase 'Order' que recibe la estrategia y delega el cálculo.
Ver respuesta de ejemplo
El patrón Strategy permite encapsular diferentes algoritmos (en este caso costos de envío) y hacerlos intercambiables. Se define una interfaz 'ShippingStrategy' con un método 'calculateCost(double weight)'. Luego se implementan distintas estrategias como 'StandardShipping' que aplica una tarifa base más un costo por peso, y 'ExpressShipping' que aplica un recargo. La clase 'Order' tiene un campo 'ShippingStrategy' y al llamar al método 'calculateShippingCost' delega en la estrategia actual. Esto permite cambiar el cálculo de envío en tiempo de ejecución sin modificar la clase Order, cumpliendo el principio abierto/cerrado.
Solución de referenciajava interface ShippingStrategy { double calculateCost(double weight); } class StandardShipping implements ShippingStrategy { public double calculateCost(double weight) { return 5.0 + 2.0 * weight; } } class ExpressShipping implements ShippingStrategy { public double calculateCost(double weight) { return 10.0 + 4.0 * weight; } } class Order { private double weight; private ShippingStrategy strategy; public Order(double weight, ShippingStrategy strategy) { this.weight = weight; this.strategy = strategy; } public double calculateShippingCost() { return strategy.calculateCost(weight); } } public class Main { public static void main(String[] args) { Order order = new Order(5, new ExpressShipping()); System.out.println("Costo de envío: " + order.calculateShippingCost()); } } - ¿Qué es el Principio de Sustitución de Liskov? Proporciona un ejemplo de código que lo viole.Lo que cubre una buena respuesta
- LSP: los objetos de una subclase deben poder reemplazar a los de la superclase sin alterar la corrección.
- Violación típica: 'Square' hereda de 'Rectangle' y modifica setters para mantener consistencia.
- El cliente que espera un 'Rectangle' se rompe al tratar con un 'Square' porque el comportamiento cambia.
Ver respuesta de ejemplo
El Principio de Sustitución de Liskov establece que los subtipos deben ser sustituibles por sus tipos base sin afectar la funcionalidad del programa. Una violación clásica es la herencia de 'Square' de 'Rectangle'. Si 'Rectangle' tiene métodos 'setWidth' y 'setHeight', al sobrescribirlos en 'Square' para que ambos modifiquen el mismo lado, se rompe la invariante del rectángulo (puede tener ancho y alto distintos). Un cliente que use un 'Rectangle' espera poder cambiar ancho y alto independientemente, pero con un 'Square' eso no ocurre, lo que lleva a resultados incorrectos. Para evitar violaciones, se debe preferir composición o rediseñar la jerarquía.
Solución de referenciajava class Rectangle { protected int width, height; public void setWidth(int w) { width = w; } public void setHeight(int h) { height = h; } public int getArea() { return width * height; } } class Square extends Rectangle { @Override public void setWidth(int w) { super.setWidth(w); super.setHeight(w); } @Override public void setHeight(int h) { super.setWidth(h); super.setHeight(h); } } public class Main { public static void main(String[] args) { Rectangle r = new Square(); r.setWidth(5); r.setHeight(10); System.out.println("Área esperada: 50, obtenida: " + r.getArea()); // 100, viola LSP } } - Diseña un sistema de pedidos de restaurante con clases para MenuItem, Order y Payment, asegurando un buen diseño POO.Lo que cubre una buena respuesta
- Clase 'MenuItem' con nombre, precio y descripción.
- Clase 'Order' que contiene una lista de 'MenuItem' y calcula el total.
- Clase abstracta 'Payment' con método 'pay', y subclases 'CashPayment', 'CreditCardPayment', etc.
- Principios: responsabilidad única, abierto/cerrado, inyección de dependencias.
Ver respuesta de ejemplo
El diseño sigue buenas prácticas POO. 'MenuItem' encapsula los datos del ítem. 'Order' gestiona la lista de ítems y calcula el total, con métodos para añadir/eliminar. 'Payment' es una abstracción para diferentes métodos de pago, permitiendo extender sin modificar el código existente (abierto/cerrado). Las subclases implementan 'pay' con lógica específica. 'Order' no depende de implementaciones concretas de pago, sino que recibe un 'Payment' por constructor (inyección de dependencias). Esto facilita pruebas y cambios. Además, cada clase tiene una única responsabilidad: representar un ítem, gestionar el pedido o procesar un pago.
Solución de referenciajava import java.util.ArrayList; import java.util.List; class MenuItem { private String name; private double price; public MenuItem(String name, double price) { this.name = name; this.price = price; } public double getPrice() { return price; } } class Order { private List<MenuItem> items = new ArrayList<>(); public void addItem(MenuItem item) { items.add(item); } public double getTotal() { return items.stream().mapToDouble(MenuItem::getPrice).sum(); } } abstract class Payment { protected double amount; public Payment(double amount) { this.amount = amount; } public abstract void pay(); } class CreditCardPayment extends Payment { public CreditCardPayment(double amount) { super(amount); } @Override public void pay() { System.out.println("Pagando " + amount + " con tarjeta de crédito."); } } class CashPayment extends Payment { public CashPayment(double amount) { super(amount); } @Override public void pay() { System.out.println("Pagando " + amount + " en efectivo."); } } public class Restaurant { public static void main(String[] args) { MenuItem pizza = new MenuItem("Pizza", 12.50); MenuItem refresco = new MenuItem("Refresco", 2.00); Order order = new Order(); order.addItem(pizza); order.addItem(refresco); double total = order.getTotal(); Payment payment = new CreditCardPayment(total); payment.pay(); } }
Cómo prepararse
- Domina las definiciones y ejemplos de cada principio POO; prepárate para explicar con analogías.
- Practica diseñando sistemas en una pizarra o usando un editor de código, enfocándote en las responsabilidades y relaciones de las clases.
- Revisa patrones de diseño comunes y sé capaz de implementarlos desde cero en tu lenguaje.
- Comprende los trade-offs: herencia vs composición, acoplamiento fuerte vs débil, y cuándo usar abstract vs interface.
- Prepárate para la codificación práctica: escribe pequeños programas POO y siéntete cómodo explicando tus decisiones de diseño.
Preguntas frecuentes
¿Cuáles son las preguntas más comunes de entrevista sobre POO?
Las preguntas comunes incluyen explicar los cuatro pilares, la diferencia entre clase abstracta e interfaz, diseñar una jerarquía de clases e implementar un patrón de diseño como Singleton.
¿Cómo debería prepararme para problemas de codificación POO?
Practica diseñando sistemas pequeños como un estacionamiento, biblioteca o carrito de e-commerce. Enfócate en la estructura de clases, encapsulamiento y uso de interfaces.
¿Necesito conocer patrones de diseño para una entrevista de POO?
Sí, especialmente para roles senior. Conoce los más comunes: Singleton, Factory, Strategy, Observer y Decorator.
¿Cuál es la diferencia entre herencia y composición?
La herencia crea una relación 'es-un', mientras que la composición crea una relación 'tiene-un'. Favorece la composición por flexibilidad y acoplamiento débil.
¿Cómo explico los conceptos POO claramente?
Usa analogías del mundo real: un auto como objeto con propiedades (color, modelo) y métodos (arrancar, detener), o una jerarquía de animales de zoológico para explicar polimorfismo.
Practica preguntas sobre OOP con retroalimentación instantánea de IA
Sube tu currículum, obtén una entrevista simulada personalizada y ve exactamente qué mejorar — gratis para empezar.