Ir al contenido
wannacode

SOLID sin dogma

En resumen: SOLID son cinco principios para un código que sobrevive a los cambios: una sola razón para cambiar, ampliar sin modificar, subtipos sustituibles, interfaces pequeñas y dependencia de abstracciones.

Problema

El cambio del correo que rompió la contabilidad

La tienda online envía al cliente una factura por correo. Cada noche también entrega a contabilidad un archivo con una línea por pedido. Una sola clase hace las dos cosas:

record Order(String number, String customer, double net) {}
class InvoiceService {
public double vat(Order order) {
return order.net() * 0.20;
}
// Contabilidad: una línea para el archivo que importa en su programa.
public String exportLine(Order order) {
double total = order.net() + vat(order);
return String.join(";", order.number(), money(order.net()), money(vat(order)), money(total));
}
// El cliente: el texto del correo con la factura.
public String emailText(Order order) {
double total = order.net() + vat(order);
return String.format("Hola, %s! Pedido %s: %s €", order.customer(), order.number(), money(total));
}
private String money(double amount) {
return String.format("%.2f", amount);
}
}
InvoiceService invoices = new InvoiceService();
Order order = new Order("2026-0142", "María", 80);
System.out.println(invoices.exportLine(order));
System.out.println(invoices.emailText(order));
Salida
2026-0142;80.00;16.00;96.00
Hola, María! Pedido 2026-0142: 96.00 €

Marketing dice: «En el correo, pon 96 €, no 96,00 €. Se ve más limpio.» Alguien cambia el método auxiliar método money:

private String money(double amount) {
if (amount == Math.floor(amount)) {
return String.valueOf((long) amount);
}
return String.format("%.2f", amount);
}
Salida
2026-0142;80;16;96
Hola, María! Pedido 2026-0142: 96 €

El correo queda como querían. A la mañana siguiente el programa de contabilidad rechaza el archivo: espera 80.00 y recibe 80. Nadie tocó la línea de contabilidad. Lo que cambió es el método auxiliar que comparten las dos líneas. La prueba del correo pasa, y no hay prueba del archivo, porque «nadie lo cambió».

La idea

Una sola razón para cambiar

La clase atiende a dos partes que no se conocen. La contable necesita un formato exacto que lee su programa. Marketing quiere un texto agradable para el cliente. Piden cambios en momentos distintos y por razones distintas. Mientras su código viva en una sola clase, un cambio para uno puede romper al otro.

Este es el primero de los cinco principios SOLID (reunidos por Robert Martin; el nombre sale de sus iniciales). El Single Responsibility Principle (SRP): una clase tiene una sola razón para cambiar.

«Razón» significa quién pide el cambio: una persona, un departamento, una ley. No significa «un método», y no significa «una clase pequeña». La pregunta que debes hacerte: «¿Quién vendrá a pedir un cambio aquí?» Si la respuesta es «contabilidad o marketing», son dos clases.

Míralo

Cada uno tiene su propia clase

El IVA y el total son una regla de la ley fiscal, y las dos partes los necesitan. Van al pedido, en un solo lugar:

record Order(String number, String customer, double net) {
public double vat() {
return net * 0.20;
}
public double total() {
return net + vat();
}
}

La línea de contabilidad y el texto del correo se convierten en clases separadas. Cada una da formato a los importes a su manera:

class AccountingExport {
public String line(Order order) {
return String.format("%s;%.2f;%.2f;%.2f", order.number(), order.net(), order.vat(), order.total());
}
}
class InvoiceEmail {
public String text(Order order) {
return String.format("Hola, %s! Pedido %s: %s €", order.customer(), order.number(), money(order.total()));
}
private String money(double amount) {
if (amount == Math.floor(amount)) {
return String.valueOf((long) amount);
}
return String.format("%.2f", amount);
}
}
Order order = new Order("2026-0142", "María", 80);
System.out.println(new AccountingExport().line(order));
System.out.println(new InvoiceEmail().text(order));
Salida
2026-0142;80.00;16.00;96.00
Hola, María! Pedido 2026-0142: 96 €

El correo tiene importes enteros, y el archivo sigue con dos decimales. Mañana contabilidad cambiará el formato, y tocarás solo AccountingExport. El código del correo ni siquiera está delante de ti.

Las dos formas de escribir un importe ya no se comparten. Eso no es duplicación: hoy se parecen, pero son dos reglas que cambian por razones distintas.

Cuándo duele. El otro extremo es una clase para cada acción: VatCalculator, TotalCalculator, NumberFormatter, GreetingBuilder, tres líneas cada una. Siempre cambian juntas, porque las pide la misma persona, y abres cuatro archivos para leer una regla. Una clase de 200 líneas que atiende a una persona es mejor que cinco clases que siempre abres juntas. Lo que cambia junto se queda junto.

Más precisamente, para curiosos
  • El propio Robert Martin escribe que «una responsabilidad» suele leerse como «hace una sola cosa». Su aclaración posterior es que un módulo responde ante un actor: un grupo de personas que piden cambios. «Hace una sola cosa» es una regla para funciones, no para clases.
  • «Don't repeat yourself» (DRY) trata del conocimiento, no del texto. Dos trozos de código que se parecen pero cambian por razones distintas no son duplicación. Si los fundes en un método auxiliar, atas a dos personas con un mismo nudo. Eso es justo lo que pasó en el problema.

Pruébalo

Quién pide el cambio

1. ¿Qué pregunta te dice si una clase tiene una sola razón para cambiar?
2. ShippingLabel calcula el precio del envío (lo pide ventas), dibuja la etiqueta (el formato es de la empresa de mensajería) y escribe la línea del almacén (la pide el almacén). ¿Cómo lo organizas?

Míralo

Un envío nuevo sin tocar el total

El segundo principio es abierto/cerrado (Open/Closed Principle, OCP): el código está abierto a la ampliación y cerrado a la modificación. Una variante nueva llega como código nuevo, y el código que ya funciona y está probado no se toca.

Lo viste en la lección Strategy: la cadena de if para el método de pago se convirtió en una tabla nombre → estrategia. Con los envíos pasa igual. Cada envío conoce su precio:

interface Delivery {
double cost(double amount);
}
class ToAddress implements Delivery {
@Override
public double cost(double amount) {
return amount >= 100 ? 0 : 5.90;
}
}
class ToOffice implements Delivery {
@Override
public double cost(double amount) {
return 3.90;
}
}

El total no sabe qué envíos existen:

static double orderTotal(double amount, Delivery delivery) {
return amount + delivery.cost(amount);
}

La tienda añade el envío a un casillero de paquetes. Es una clase nueva:

class ToLocker implements Delivery {
@Override
public double cost(double amount) {
return 2.90;
}
}

orderTotal, el envío a un domicilio y a una oficina no se tocan. Falta poner el envío nuevo en la tabla de la que elige el cliente. Esa es la siguiente tarjeta.

Cuándo duele. No puedes cerrar el código contra todo, y no deberías. Cada punto de extensión es una interfaz, una clase y una fila en una tabla que alguien tiene que leer. Cierras donde los cambios llegan de verdad: cada temporada llegan envíos nuevos. La regla práctica: la primera vez, escribe un if. Cuando llegue la tercera variante, haz la tabla (Replace Conditional with Polymorphism, de la lección sobre refactorización).

Pruébalo

Una fila nueva en la tabla

La tabla de la lección Strategy enumera los envíos entre los que elige el cliente. Añade el casillero.

¿Qué va en el hueco?

Map<String, Delivery> deliveries = Map.of(
        "domicilio", new ToAddress(),
        "oficina", new ToOffice(),
        "casillero", );

for (String choice : List.of("domicilio", "oficina", "casillero")) {
    System.out.printf("%s: %.2f €%n", choice, orderTotal(80, deliveries.get(choice)));
}

Problema

La cancelación que se detuvo a medias

Una clienta paga un pedido de tres formas: 60 € con tarjeta, un cupón de 20 € y 16 € en puntos. Después lo cancela. Cada tipo de pago sabe cómo devolver su dinero:

interface Payment {
// Devuelve el dinero al cliente por el mismo medio con el que pagó.
void refund();
}
record CardPayment(double amount) implements Payment {
@Override
public void refund() {
System.out.printf("Tarjeta: devueltos %.2f €%n", amount);
}
}
record VoucherPayment(double amount) implements Payment {
@Override
public void refund() {
throw new UnsupportedOperationException("un cupón no se puede devolver");
}
}
record PointsPayment(double amount) implements Payment {
@Override
public void refund() {
System.out.printf("Puntos: devueltos %.2f €%n", amount);
}
}

El pedido recorre sus pagos y llama a refund() en cada uno:

class Order {
private final List<Payment> payments;
String status = "pagado";
Order(List<Payment> payments) {
this.payments = payments;
}
void cancel() {
for (Payment payment : payments) {
payment.refund();
}
status = "cancelado";
}
}
Order order = new Order(List.of(
new CardPayment(60),
new VoucherPayment(20),
new PointsPayment(16)));
try {
order.cancel();
} catch (UnsupportedOperationException e) {
System.out.println(
"Error: " + e.getMessage());
}
System.out.println("Estado: " + order.status);
Salida
Tarjeta: devueltos 60.00 €
Error: un cupón no se puede devolver
Estado: pagado

La tarjeta está devuelta, los puntos no, y el pedido sigue «pagado». El almacén ve «pagado» y envía el paquete. El código de cancel() es correcto. El fallo está en el cupón: es un Payment, pero no hace lo que Payment promete.

Míralo

El subtipo que rompe la promesa

void cancel() {
for (Payment payment : payments) {
payment.refund();
}
status = "cancelado";
}
Order order = new Order(List.of(
new CardPayment(60),
new VoucherPayment(20),
new PointsPayment(16)));
try {
order.cancel();
} catch (UnsupportedOperationException e) {
System.out.println(
"Error: " + e.getMessage());
}
System.out.println("Estado: " + order.status);
Paso a paso, en palabras
  1. El pedido se pagó de tres formas: 60.00 € con tarjeta, un cupón de 20.00 € y 16.00 € en puntos. Los tres son Payment.
  2. La clienta cancela. cancel() recorre los pagos y llama a refund() en cada uno.
  3. La tarjeta devuelve 60.00 €. Hace exactamente lo que promete Payment.
  4. El cupón también es un Payment, pero su refund() lanza UnsupportedOperationException. La promesa está rota.
  5. La excepción sale del bucle y de cancel(). Los puntos (16.00 €) no se devuelven, y la línea que pone status en «cancelado» nunca se ejecuta.
  6. El catch en main la atrapa y imprime el error. El programa sigue.
  7. El estado sigue siendo «pagado». El almacén envía el pedido, y 60.00 € ya están de vuelta en la tarjeta.

Este es el tercer principio, el principio de sustitución de Barbara Liskov (Liskov Substitution Principle, LSP): donde el código trabaja con un tipo, cualquier subtipo puede ocupar su lugar, y el código no lo nota. El subtipo puede hacer el trabajo a su manera, pero cumple la promesa del tipo.

Payment promete «devuelvo el dinero al cliente». ¿Qué significa eso para un cupón en una tienda de verdad? Un cupón nuevo por el mismo importe:

record VoucherPayment(double amount) implements Payment {
@Override
public void refund() {
System.out.printf("Cupón: nuevo cupón por %.2f €%n", amount);
}
}
Order order = new Order(List.of(
new CardPayment(60),
new VoucherPayment(20),
new PointsPayment(16)));
try {
order.cancel();
} catch (UnsupportedOperationException e) {
System.out.println(
"Error: " + e.getMessage());
}
System.out.println("Estado: " + order.status);
Salida
Tarjeta: devueltos 60.00 €
Cupón: nuevo cupón por 20.00 €
Puntos: devueltos 16.00 €
Estado: cancelado

cancel() no cambió. Ahora el subtipo encaja donde se espera el tipo.

Reconoces una violación por tres señales:

  • el subtipo lanza «no soportado» (UnsupportedOperationException) en un método que el tipo promete;
  • el subtipo devuelve algo que el tipo no promete (-1, null, una cadena vacía), o le exige más a quien llama;
  • el código comprueba con instanceof antes de llamar al método. Es un rodeo alrededor de un subtipo que no encaja.
El compilador no ve ninguna de las tres. Comprueba que el método esté escrito, no que haga lo que promete.

Cuándo duele. Aquí el dogma va en la otra dirección: meter algo a la fuerza en la jerarquía porque «también es un pago». Si algo de verdad no puede devolver dinero (un regalo de la tienda, por ejemplo), no es un Payment con refund. Dale su propio tipo en lugar de escribir un método que lanza.

Más precisamente, para curiosos
La biblioteca estándar también tiene un subtipo así. List.of te da una List que no admite add:
List<String> items = List.of("café", "taza");
try {
items.add("té");
} catch (UnsupportedOperationException e) {
System.out.println("List.of no admite add: " + e.getClass().getSimpleName());
}
Salida
List.of no admite add: UnsupportedOperationException
Por eso la biblioteca lo dice en su documentación: el método es una «operación opcional». Si tu tipo tiene un método que a veces no funciona, dilo en el nombre del tipo o divídelo en dos.

Formalmente, según Liskov: un subtipo no pide más que el tipo (no endurece las condiciones de entrada), no da menos (no debilita la promesa sobre el resultado) y conserva lo que siempre es cierto para el tipo.

Pruébalo

La violación silenciosa

El envío a un casillero devuelve -1 cuando el casillero está lleno. El total lo suma, como siempre. ¿Qué imprime el programa?

class ToLocker implements Delivery {
private final boolean full;
public ToLocker(boolean full) {
this.full = full;
}
@Override
public double cost(double amount) {
if (full) {
return -1; // -1 significa «no hay sitio»
}
return 2.90;
}
}
System.out.printf("%.2f €%n", orderTotal(80, new ToLocker(true)));
Respuesta

Problema

El SMS que nunca salió

La tienda envía avisos a los clientes. Alguien describió en un solo lugar todo lo que se puede enviar:

interface Notifier {
void email(String to, String text);
void sms(String phone, String text);
}
El proveedor de correo solo envía correos, pero la interfaz pide también SMS. Sin el método la clase no compila, así que alguien lo escribió.

Pruébalo

Encuentra el error

El cliente recibe un correo de que el paquete salió. El SMS del mensajero nunca llega:

Notifier notifier = new MailProvider();
notifier.email("maria@example.com", "El pedido 2026-0142 ha sido enviado.");
notifier.sms("0888 123 456", "El mensajero llega hoy entre las 14 y las 16 h.");
Salida
Correo a maria@example.com: El pedido 2026-0142 ha sido enviado.

¿En qué línea está el error?

Elige la línea con el error.

Míralo

Interfaces pequeñas

El cuarto principio: segregación de interfaces (Interface Segregation Principle, ISP). Nadie debe depender de métodos que no usa. El contrato se escribe desde el lado de quien lo usa, y es tan grande como lo necesita.

El correo y el SMS se convierten en dos interfaces:

interface EmailSender {
void email(String to, String text);
}
interface SmsSender {
void sms(String phone, String text);
}

Cada proveedor hace solo lo que sabe, y el aviso del mensajero pide solo SMS:

class MailProvider implements EmailSender {
@Override
public void email(String to, String text) {
System.out.println("Correo a " + to + ": " + text);
}
}
class SmsGateway implements SmsSender {
@Override
public void sms(String phone, String text) {
System.out.println("SMS a " + phone + ": " + text);
}
}
class ShippingNotice {
private final SmsSender sender;
public ShippingNotice(SmsSender sender) {
this.sender = sender;
}
public void courierToday(String phone) {
sender.sms(phone, "El mensajero llega hoy entre las 14 y las 16 h.");
}
}
EmailSender email = new MailProvider();
email.email("maria@example.com", "El pedido 2026-0142 ha sido enviado.");
new ShippingNotice(new SmsGateway()).courierToday("0888 123 456");
Salida
Correo a maria@example.com: El pedido 2026-0142 ha sido enviado.
SMS a 0888 123 456: El mensajero llega hoy entre las 14 y las 16 h.

Si alguien pasa el proveedor de correo donde hace falta un SMS, el programa no compila:

new ShippingNotice(new MailProvider()).courierToday("0888 123 456");
Error
error: incompatible types: MailProvider cannot be converted to SmsSender

El error de la tarjeta anterior es imposible: nadie pide un método vacío.

Las interfaces pequeñas también ayudan a las pruebas: el doble del aviso implementa un método, no todo lo que la tienda puede enviar. Una clase puede implementar las dos si el proveedor de verdad envía correos y SMS: class MailAndSms implements EmailSender, SmsSender.

Cuándo duele. Una interfaz por cada método «por ISP» es el otro extremo: Readable, Writable, Deletable, Countable delante de un repositorio que se usa en un solo sitio y siempre entero. Si todos los que usan el contrato lo usan todo, es un solo contrato.

Problema

La prueba que envía un correo de verdad

La factura sale por correo. El servicio crea su propio mailer:

class SmtpMailer {
public void send(String to, String text) {
System.out.println("SMTP a " + to + ": " + text);
}
}
class InvoiceService {
private final SmtpMailer mailer = new SmtpMailer();
public void issue(Order order, String email) {
mailer.send(email, "Factura %s: %.2f €".formatted(
order.number(), order.total()));
}
}
Order order = new Order("2026-0142", "María", 80);
new InvoiceService().issue(order, "maria@example.com");
Salida
SMTP a maria@example.com: Factura 2026-0142: 96.00 €

Funciona, pero:

  • la prueba de la factura envía un correo de verdad, y en una máquina sin servidor SMTP falla;
  • la tienda pasa de SMTP a un servicio con API, y editas la clase con las reglas de la factura para cambiar el mailer.

La regla (qué va en la factura) depende del detalle (cómo sale el correo).

Míralo

La abstracción pertenece a la regla

El quinto principio: inversión de dependencias (Dependency Inversion Principle, DIP). El código con las reglas no depende de los detalles. Ambos dependen de una abstracción, y ella pertenece a las reglas: dice qué necesitan («envía este texto a esta dirección»), no cómo lo hace SMTP.

interface InvoiceSender {
void send(String to, String text);
}
class InvoiceService {
private final InvoiceSender sender;
public InvoiceService(InvoiceSender sender) {
this.sender = sender;
}
public void issue(Order order, String email) {
sender.send(email, "Factura %s: %.2f €".formatted(
order.number(), order.total()));
}
}

Los detalles van aparte. El mailer y el doble de prueba implementan la abstracción del servicio:

class SmtpSender implements InvoiceSender {
@Override
public void send(String to, String text) {
System.out.println("SMTP a " + to + ": " + text);
}
}
class FakeSender implements InvoiceSender {
final List<String> sent = new ArrayList<>();
@Override
public void send(String to, String text) {
sent.add(to + " | " + text);
}
}
Order order = new Order("2026-0142", "María", 80);
new InvoiceService(new SmtpSender())
.issue(order, "maria@example.com");
FakeSender fake = new FakeSender();
InvoiceService service = new InvoiceService(fake);
service.issue(order, "maria@example.com");
System.out.println("La prueba vio: " + fake.sent.get(0));
Salida
SMTP a maria@example.com: Factura 2026-0142: 96.00 €
La prueba vio: maria@example.com | Factura 2026-0142: 96.00 €

interface InvoiceSender {
void send(String to, String text);
}
class InvoiceService {
private final InvoiceSender sender;
public InvoiceService(InvoiceSender sender) {
this.sender = sender;
}
public void issue(Order order, String email) {
sender.send(email, "Factura %s: %.2f €".formatted(
order.number(), order.total()));
}
}
class SmtpSender implements InvoiceSender {
@Override
public void send(String to, String text) {
System.out.println("SMTP a " + to + ": " + text);
}
}
Order order = new Order("2026-0142", "María", 80);
new InvoiceService(new SmtpSender())
.issue(order, "maria@example.com");
FakeSender fake = new FakeSender();
InvoiceService service = new InvoiceService(fake);
service.issue(order, "maria@example.com");
System.out.println("La prueba vio: " + fake.sent.get(0));
Paso a paso, en palabras
  1. Antes las facturas apuntaban hacia abajo, a SmtpMailer. Ahora el servicio anota lo que necesita: InvoiceSender, junto a sí mismo.
  2. InvoiceService recibe el envío en el constructor y solo conoce InvoiceSender. De SMTP no sabe nada.
  3. SmtpSender implementa el InvoiceSender del servicio. La flecha del código apunta hacia arriba: el detalle depende de la regla. Esa es la inversión.
  4. En ejecución la llamada sigue yendo hacia abajo: del servicio a SMTP. Lo que se invierte es la dependencia en el código, no la dirección de la llamada.
  5. En la prueba el mismo servicio recibe FakeSender: un segundo detalle que apunta a la misma abstracción. No sale ningún correo: el doble anota en sent lo que recibió.
  6. La prueba ve exactamente el texto de la factura, sin servidor de correo.

La «inversión» es la de la flecha en el código. Antes, las facturas conocían SMTP. Ahora SMTP conoce InvoiceSender, que está junto a las facturas. En ejecución, la llamada sigue yendo de las facturas a SMTP.

DIP y DI. En la lección Singleton e inyección de dependencias viste pasar desde fuera: el objeto recibe su dependencia en el constructor. Ese es el mecanismo. DIP dice de qué depender: de una abstracción que es tuya. Puedes tener una sin la otra:

  • pasar sin DIP: InvoiceService(SmtpMailer mailer). El mailer viene de fuera, pero el servicio sigue conociendo la clase SMTP, y una prueba sin SMTP no es posible;
  • DIP sin una biblioteca de DI: en main, InvoiceService recibe SmtpSender, y ese es todo el cableado. Hay un solo lugar donde el programa junta los objetos.

Cuándo duele. Una interfaz delante de cada clase «por DIP» es el dogma más común. Una abstracción vale la pena delante de un detalle lento, externo o que va a cambiar: correo, pago, base de datos, reloj, red. Delante de Order o delante del formato de un importe es código que leer sin beneficio.

Más precisamente, para curiosos
En un proyecto real InvoiceSender vive en el paquete de las facturas (shop.invoices), y SmtpSender vive en shop.mail e importa shop.invoices.InvoiceSender. El paquete de las facturas no importa nada del correo. Esa es la flecha invertida, visible en los imports.

Pruébalo

Pon la prueba en orden

La prueba comprueba la factura sin un mailer real. El order ya existe. Pon las líneas en orden:

  1. InvoiceService service = new InvoiceService(fake);
  2. FakeSender fake = new FakeSender();
  3. System.out.println("La prueba vio: " + fake.sent.get(0));
  4. service.issue(order, "maria@example.com");

En resumen

Cuando SOLID duele

Aquí está el IVA, escrito «según todas las reglas»:

interface TaxPolicy {
double vat(double net);
}
class StandardVat implements TaxPolicy {
@Override
public double vat(double net) {
return net * 0.20;
}
}
class TaxPolicyFactory {
public static TaxPolicy create() {
return new StandardVat();
}
}
System.out.printf("IVA: %.2f €%n", TaxPolicyFactory.create().vat(80));
Salida
IVA: 16.00 €

Y lo mismo sin dogma:

static final double VAT_RATE = 0.20;
System.out.printf("IVA: %.2f €%n", 80 * VAT_RATE);
Salida
IVA: 16.00 €

La salida es la misma. Lo primero es una interfaz, una clase y una fábrica para un número que cambia una vez cada diez años, y de todos modos en un solo lugar. Ese es el smell llamado generalidad especulativa (speculative generality): código para cambios que nadie ha pedido. Cuando llegue un segundo tipo (9 % para alimentos), ese es el momento de una tabla.

Los principios son preguntas, no reglas. Hazlas cuando el código duela:

La pregunta El dogma que debes evitar
SRP ¿Quién pedirá un cambio aquí? ¿Una parte o dos? Una clase por cada método
OCP ¿Qué llega de verdad nuevo cada mes? Puntos de extensión por todas partes
LSP ¿Puede cada subtipo estar aquí sin un if? Una jerarquía por la palabra «es»
ISP ¿Este cliente usa todo lo que pide? Una interfaz por cada método
DIP ¿El detalle es lento, externo o reemplazable? Una interfaz delante de cada clase

Ejercicio

Una tarjeta regalo sin tocar la cancelación

Resuelve la tarea en tu editor.

Tarjeta regalo. La tienda también acepta tarjetas regalo. Al cancelar, el dinero vuelve a la tarjeta. Añade un GiftCardPayment que cumpla la promesa de Payment, y cancela un pedido pagado con 40 € con tarjeta y 25 € con tarjeta regalo. cancel() y los otros pagos no se tocan.

Salida
Tarjeta: devueltos 40.00 €
Tarjeta regalo: 25.00 € de vuelta en la tarjeta
Estado: cancelado

Más tareas

  • El informe mensual. Una clase calcula los ingresos del mes, escribe un CSV para contabilidad y un texto para la gerente, y envía el correo ella misma. Divídela según quién pide los cambios. ¿Qué queda compartido?
  • Notificación push. Añade en la aplicación móvil un aviso cuando el paquete ha salido. ¿Qué contrato pequeño necesitas, y qué clases no tocas?
  • Quitar una abstracción. Busca en tu propio proyecto una interfaz con una sola implementación sin ningún detalle lento o externo detrás. Quita la envoltura y ejecuta las pruebas.

Test

Ponte a prueba

1. Marketing pide un cambio en el texto del correo, y después se rompe el archivo de contabilidad. ¿Qué principio se ha roto?
2. La tienda añade el envío a un casillero de paquetes. ¿Qué significa «cerrado a la modificación»?
3. VoucherPayment implementa Payment, pero refund lanza «no soportado». ¿Qué pasa con el código que llama a refund en cada pago?
4. El proveedor de correo tiene que escribir un método sms vacío para implementar Notifier. ¿Qué te dice eso?
5. InvoiceService(SmtpMailer mailer) recibe el mailer en el constructor. ¿Cumple DIP?
6. ¿Cuándo vale la pena una interfaz con una sola implementación?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·