Práctica asincrónica · S7 V · viernes 26 de junio
Práctica: Adapter vs Facade
Cuatro ejercicios para decidir, no para leer. Dado un código que huele, elegís si va Adapter o Facade (y por qué), lo refactorizás en un editor PHP real, y recién después revelás la solución. Uno es trampa — un caso donde no va ningún patrón. Distinguir los dos es justo lo que evalúa la Mini-entrega 4.
Cómo se trabaja cada ejercicio
- Decidí — escribí en el recuadro si ves Adapter, Facade o ninguno, antes de ver la respuesta. El veredicto está oculto a propósito.
- Refactorizá — abrí el código en php.cesar.sh, escribí tu solución y ejecutala de verdad.
- Compará — revelá el veredicto, la solución modelo y el “por qué”. ¿Acertaste el criterio?
La pregunta que separa los dos patrones: ¿el problema es la forma de UNO (Adapter), o la cantidad de cosas en orden de MUCHOS (Facade)? Esta práctica es el insumo directo para la Mini-entrega 4.
Adapter vs Facade
Cuatro casos: uno donde algo habla otro idioma (Adapter), uno donde el cliente coordina un subsistema (Facade), una trampa donde no va ningún patrón, y uno mixto donde los dos conviven en capas distintas.
Una SDK de envíos de terceros (FedExSDK) expone requestQuote() y devuelve un array crudo (cost_cents, eta_days). Tu código quiere hablar en su propio idioma —$shipping->quote($weight): Quote— pero hoy llama directo a la SDK y mapea cost_cents a mano, regado en 3 controllers.
// librería de terceros — no la podés tocar
class FedExSDK
{
public function requestQuote(array $pkg): array
{
// → ['cost_cents' => 1250, 'eta_days' => 3]
return ['cost_cents' => 1250, 'eta_days' => 3];
}
}
// tu código quiere hablar ASÍ: $shipping->quote($weight): Quote
// pero hoy llama directo a requestQuote() y mapea cost_cents a mano,
// regado en 3 controllers distintos. Mostrar pista
Una sola clase, útil, con la interfaz equivocada. ¿Qué patrón pone un traductor en medio sin tocar la clase ajena?
Compará tu diagnóstico del Paso 1 con esto. ¿Acertaste el concepto?
Ver solución modelo en código
interface Shipping
{
public function quote(float $weight): Quote;
}
class FedExAdapter implements Shipping
{
public function __construct(private FedExSDK $sdk) {}
public function quote(float $weight): Quote
{
$r = $this->sdk->requestQuote(['weight' => $weight]);
// traduce el idioma ajeno (centavos) al tuyo (dólares)
return new Quote($r['cost_cents'] / 100, $r['eta_days']);
}
}
// tu código depende de Shipping, no de FedExSDK.
// agregar otra paquetería = un adapter nuevo, cero cambios en los controllers. Publicar un post toca cuatro servicios en orden: guardar en DB, indexar en el buscador, invalidar cache y avisar a seguidores. Cada servicio ya hace bien su trabajo — el problema es que el PostController conoce el orden, y ese mismo orden se repite en la API, el import masivo y el cron de programados.
class PostController
{
public function publish(Post $post): void
{
$this->db->save($post); // 1. guardar
$this->search->index($post); // 2. indexar en buscador
$this->cache->bust('posts'); // 3. invalidar cache
$this->notifier->fanout($post); // 4. avisar a seguidores
}
}
// el MISMO orden se repite en: publicar por API, import masivo, cron de programados Mostrar pista
Nadie habla otro idioma acá. El smell es el orden de muchos servicios, repetido en cada cliente. ¿Quién debería conocer esa coreografía?
Compará tu diagnóstico del Paso 1 con esto. ¿Acertaste el concepto?
Ver solución modelo en código
class PublishingFacade
{
public function __construct(
private Db $db,
private Search $search,
private Cache $cache,
private Notifier $notifier,
) {}
public function publish(Post $post): void
{
$this->db->save($post);
$this->search->index($post);
$this->cache->bust('posts');
$this->notifier->fanout($post);
}
}
// PostController::publish() queda en una línea:
// $this->publishing->publish($post);
// el orden vive en UN solo lugar; la facade delega, no implementa. Una librería del banco expone BalanceClient::getBalance(string $account): float — una sola clase, una sola operación, que ya devuelve un float. Tu Wallet::show() llama a getBalance() y devuelve ese float directo, sin tocar nada. Alguien propone "envolvámoslo en un Adapter o una Facade para desacoplar".
// librería del banco — una sola clase, una sola operación
class BalanceClient
{
public function getBalance(string $account): float { /* ... */ return 0.0; }
}
// tu código:
class Wallet
{
public function show(BalanceClient $c, string $acc): float
{
return $c->getBalance($acc); // ya devuelve un float, justo lo que querés
}
} Mostrar pista
Adapter resuelve interfaz incompatible. Facade resuelve coordinar muchos. ¿Cuál de los dos problemas tenés acá... o ninguno?
Compará tu diagnóstico del Paso 1 con esto. ¿Acertaste el concepto?
Ver solución modelo en código
// Respuesta: NO hace falta ningún patrón. Llamá directo.
//
// No es Adapter: la interfaz YA coincide — getBalance() devuelve
// el float que tu código espera. No hay nada que traducir.
//
// No es Facade: hay UNA sola operación, no un subsistema con
// orden. No hay coreografía que esconder.
//
// Envolverlo sería burocracia de código: una capa que no aporta,
// que hace el flujo más difícil de seguir, no más fácil.
//
// (Si mañana getBalance devolviera otro formato, o hubiera que
// coordinar varias llamadas, AHÍ recién entraría un patrón.) Un CheckoutController coordina tres subsistemas en orden (reservar stock, calcular total, notificar) y, en medio, cobra con una pasarela local —PuntoVentaSV::debitar()— que habla otro idioma (centavos, colones, resultado en español). Dos problemas distintos en el mismo método.
class CheckoutController
{
public function checkout(Cart $cart): void
{
$this->stock->reserve($cart); // subsistema
$total = $this->pricing->total($cart); // subsistema
// y para cobrar, la pasarela local habla raro:
$r = (new PuntoVentaSV())->debitar($total * 100, 'colones'); // ¡otro idioma!
$ok = $r['resultado'] === 'OK';
$this->notify->send($cart); // subsistema
}
} Mostrar pista
Hay dos problemas a la vez: un orden de subsistemas (¿qué patrón?) y una pasarela que habla raro metida en medio (¿qué otro patrón?).
Compará tu diagnóstico del Paso 1 con esto. ¿Acertaste el concepto?
Ver solución modelo en código
// Adapter — para la pasarela que habla otro idioma:
interface PaymentGateway { public function charge(float $amount): bool; }
class PuntoVentaAdapter implements PaymentGateway
{
public function __construct(private PuntoVentaSV $pv) {}
public function charge(float $amount): bool
{
$r = $this->pv->debitar($amount * 100, 'colones');
return $r['resultado'] === 'OK'; // traduce al idioma tuyo
}
}
// Facade — para el orden de los subsistemas:
class CheckoutFacade
{
public function __construct(
private Stock $stock, private Pricing $pricing,
private PaymentGateway $payment, private Notifier $notify,
) {}
public function checkout(Cart $cart): void
{
$this->stock->reserve($cart);
$total = $this->pricing->total($cart);
$this->payment->charge($total); // adentro vive el adapter
$this->notify->send($cart);
}
}
// CheckoutController queda en una línea: $this->checkout->checkout($cart); Lo que sigue
Mini-entrega 4
Ya distinguís Adapter de Facade — y cuándo no va ninguno. Ahora aplicalo sobre el legacy real: pagos → Adapter (cada pasarela habla otro idioma) y checkout → Facade (placeOrder coordina 6 subsistemas). Vos detectás dónde encaja cada patrón — es parte del trabajo. Con bitácora de 4 preguntas por módulo.
Cierra jueves 2 de julio, 23:59 · 7% · branch me4 + PR a main + tag v4-adapter-facade