Salta al contenuto principale

03 - Adapter, Decorator e Facade Pattern: Strutture Flessibili

Pattern strutturali essenziali: Adapter per compatibilità, Decorator per estensibilità dinamica e Facade per semplificazione di sistemi complessi.

Cosa ne pensi di questo articolo?

03 - Adapter, Decorator e Facade Pattern: Strutture Flessibili
Design PatternsFoto:Ruben BoekelooPexels

In Breve

Pattern strutturali essenziali: Adapter per compatibilità, Decorator per estensibilità dinamica e Facade per semplificazione di sistemi complessi.

Adapter, Decorator e Facade Pattern

I pattern strutturali si occupano della composizione di classi e oggetti. Adapter rende compatibili interfacce incompatibili, Decorator aggiunge responsabilità dinamicamente, e Facade fornisce un'interfaccia semplificata a un sistema complesso.

🎯 Cosa Imparerai

  • Adapter pattern: compatibilità tra interfacce diverse
  • Decorator pattern: estendere funzionalità dinamicamente
  • Facade pattern: semplificare sistemi complessi
  • Quando usare ciascun pattern strutturale

Adapter Pattern

L'Adapter pattern (o Wrapper) converte l'interfaccia di una classe in un'altra interfaccia che i client si aspettano. Permette a classi con interfacce incompatibili di collaborare.

TypeScript - Adapter Base
// Target: interfaccia che il client si aspetta
interface MediaPlayer {{ '{' }}
  play(fileName: string): void;
{{ '}' }}

// Adaptee: classe esistente con interfaccia incompatibile
class Mp3Player {{ '{' }}
  playMp3(fileName: string): void {{ '{' }}
    console.log(`🎵 Playing MP3: ${{ '{' }}fileName{{ '}' }}`);
  {{ '}' }}
{{ '}' }}

class Mp4Player {{ '{' }}
  playMp4(fileName: string): void {{ '{' }}
    console.log(`🎬 Playing MP4: ${{ '{' }}fileName{{ '}' }}`);
  {{ '}' }}
{{ '}' }}

class VlcPlayer {{ '{' }}
  playVlc(fileName: string): void {{ '{' }}
    console.log(`📺 Playing VLC: ${{ '{' }}fileName{{ '}' }}`);
  {{ '}' }}
{{ '}' }}

// Adapter: adatta l'interfaccia
class Mp4Adapter implements MediaPlayer {{ '{' }}
  private mp4Player: Mp4Player;

  constructor() {{ '{' }}
    this.mp4Player = new Mp4Player();
  {{ '}' }}

  play(fileName: string): void {{ '{' }}
    // Adatta la chiamata
    this.mp4Player.playMp4(fileName);
  {{ '}' }}
{{ '}' }}

class VlcAdapter implements MediaPlayer {{ '{' }}
  private vlcPlayer: VlcPlayer;

  constructor() {{ '{' }}
    this.vlcPlayer = new VlcPlayer();
  {{ '}' }}

  play(fileName: string): void {{ '{' }}
    this.vlcPlayer.playVlc(fileName);
  {{ '}' }}
{{ '}' }}

// Client: usa l'interfaccia target
class AudioPlayer implements MediaPlayer {{ '{' }}
  private mp3Player: Mp3Player = new Mp3Player();

  play(fileName: string): void {{ '{' }}
    const extension = fileName.split('.').pop()?.toLowerCase();

    if (extension === 'mp3') {{ '{' }}
      this.mp3Player.playMp3(fileName);
    {{ '}' }} else if (extension === 'mp4') {{ '{' }}
      const adapter = new Mp4Adapter();
      adapter.play(fileName);
    {{ '}' }} else if (extension === 'vlc') {{ '{' }}
      const adapter = new VlcAdapter();
      adapter.play(fileName);
    {{ '}' }} else {{ '{' }}
      console.log(`❌ Format not supported: ${{ '{' }}extension{{ '}' }}`);
    {{ '}' }}
  {{ '}' }}
{{ '}' }}

// Utilizzo
const player = new AudioPlayer();
player.play("song.mp3");    // 🎵 Playing MP3: song.mp3
player.play("video.mp4");   // 🎬 Playing MP4: video.mp4
player.play("movie.vlc");   // 📺 Playing VLC: movie.vlc
player.play("file.avi");    // ❌ Format not supported: avi

Adapter per API Esterne

Uso pratico: adattare API di terze parti alla tua interfaccia:

Payment API Adapter
// Interfaccia interna del tuo sistema
interface PaymentProcessor {{ '{' }}
  processPayment(amount: number, currency: string): Promise<{{ '{' }} success: boolean; transactionId: string {{ '}' }}>;
{{ '}' }}

// API esterna Stripe (simulata)
class StripeAPI {{ '{' }}
  async charge(cents: number, currencyCode: string): Promise<{{ '{' }} id: string; status: string {{ '}' }}> {{ '{' }}
    console.log(`💳 Stripe: Charging ${{ '{' }}cents / 100{{ '}' }} ${{ '{' }}currencyCode{{ '}' }}`);
    return {{ '{' }} id: 'stripe_' + Math.random(), status: 'succeeded' {{ '}' }};
  {{ '}' }}
{{ '}' }}

// API esterna PayPal (simulata)
class PayPalAPI {{ '{' }}
  async createPayment(amount: number, currency: string): Promise<{{ '{' }} paymentId: string; state: string {{ '}' }}> {{ '{' }}
    console.log(`🅿️ PayPal: Creating payment ${{ '{' }}amount{{ '}' }} ${{ '{' }}currency{{ '}' }}`);
    return {{ '{' }} paymentId: 'paypal_' + Math.random(), state: 'approved' {{ '}' }};
  {{ '}' }}
{{ '}' }}

// Adapter per Stripe
class StripeAdapter implements PaymentProcessor {{ '{' }}
  private stripe: StripeAPI;

  constructor() {{ '{' }}
    this.stripe = new StripeAPI();
  {{ '}' }}

  async processPayment(amount: number, currency: string): Promise<{{ '{' }} success: boolean; transactionId: string {{ '}' }}> {{ '{' }}
    // Stripe usa centesimi invece di dollari
    const cents = Math.round(amount * 100);
    const result = await this.stripe.charge(cents, currency.toUpperCase());

    return {{ '{' }}
      success: result.status === 'succeeded',
      transactionId: result.id
    {{ '}' }};
  {{ '}' }}
{{ '}' }}

// Adapter per PayPal
class PayPalAdapter implements PaymentProcessor {{ '{' }}
  private paypal: PayPalAPI;

  constructor() {{ '{' }}
    this.paypal = new PayPalAPI();
  {{ '}' }}

  async processPayment(amount: number, currency: string): Promise<{{ '{' }} success: boolean; transactionId: string {{ '}' }}> {{ '{' }}
    const result = await this.paypal.createPayment(amount, currency.toLowerCase());

    return {{ '{' }}
      success: result.state === 'approved',
      transactionId: result.paymentId
    {{ '}' }};
  {{ '}' }}
{{ '}' }}

// Client: usa l'interfaccia unificata
class CheckoutService {{ '{' }}
  constructor(private processor: PaymentProcessor) {{ '{' }}{{ '}' }}

  async checkout(amount: number): Promise<void> {{ '{' }}
    console.log(`🛒 Processing checkout: $${{ '{' }}amount{{ '}' }}`);
    const result = await this.processor.processPayment(amount, 'USD');

    if (result.success) {{ '{' }}
      console.log(`✅ Payment successful! Transaction: ${{ '{' }}result.transactionId{{ '}' }}`);
    {{ '}' }} else {{ '{' }}
      console.log(`❌ Payment failed`);
    {{ '}' }}
  {{ '}' }}
{{ '}' }}

// Utilizzo: switch tra provider senza modificare CheckoutService
const stripeCheckout = new CheckoutService(new StripeAdapter());
await stripeCheckout.checkout(99.99);
// 💳 Stripe: Charging 9999 USD
// ✅ Payment successful! Transaction: stripe_0.123...

const paypalCheckout = new CheckoutService(new PayPalAdapter());
await paypalCheckout.checkout(49.99);
// 🅿️ PayPal: Creating payment 49.99 usd
// ✅ Payment successful! Transaction: paypal_0.456...

Decorator Pattern

Il Decorator pattern attacca responsabilità aggiuntive a un oggetto dinamicamente. I Decorator forniscono un'alternativa flessibile all'ereditarietà per estendere funzionalità.

TypeScript - Decorator Base
// Component: interfaccia base
interface Coffee {{ '{' }}
  cost(): number;
  description(): string;
{{ '}' }}

// Concrete Component
class SimpleCoffee implements Coffee {{ '{' }}
  cost(): number {{ '{' }}
    return 2;
  {{ '}' }}

  description(): string {{ '{' }}
    return "Simple coffee";
  {{ '}' }}
{{ '}' }}

// Decorator astratto
abstract class CoffeeDecorator implements Coffee {{ '{' }}
  constructor(protected coffee: Coffee) {{ '{' }}{{ '}' }}

  abstract cost(): number;
  abstract description(): string;
{{ '}' }}

// Concrete Decorators
class MilkDecorator extends CoffeeDecorator {{ '{' }}
  cost(): number {{ '{' }}
    return this.coffee.cost() + 0.5;
  {{ '}' }}

  description(): string {{ '{' }}
    return this.coffee.description() + ", milk";
  {{ '}' }}
{{ '}' }}

class SugarDecorator extends CoffeeDecorator {{ '{' }}
  cost(): number {{ '{' }}
    return this.coffee.cost() + 0.2;
  {{ '}' }}

  description(): string {{ '{' }}
    return this.coffee.description() + ", sugar";
  {{ '}' }}
{{ '}' }}

class WhipDecorator extends CoffeeDecorator {{ '{' }}
  cost(): number {{ '{' }}
    return this.coffee.cost() + 0.7;
  {{ '}' }}

  description(): string {{ '{' }}
    return this.coffee.description() + ", whipped cream";
  {{ '}' }}
{{ '}' }}

// Utilizzo: composizione dinamica
let coffee: Coffee = new SimpleCoffee();
console.log(`${{ '{' }}coffee.description(){{ '}' }} - $${{ '{' }}coffee.cost(){{ '}' }}`);
// "Simple coffee - $2"

// Aggiungi latte
coffee = new MilkDecorator(coffee);
console.log(`${{ '{' }}coffee.description(){{ '}' }} - $${{ '{' }}coffee.cost(){{ '}' }}`);
// "Simple coffee, milk - $2.5"

// Aggiungi zucchero
coffee = new SugarDecorator(coffee);
console.log(`${{ '{' }}coffee.description(){{ '}' }} - $${{ '{' }}coffee.cost(){{ '}' }}`);
// "Simple coffee, milk, sugar - $2.7"

// Aggiungi panna
coffee = new WhipDecorator(coffee);
console.log(`${{ '{' }}coffee.description(){{ '}' }} - $${{ '{' }}coffee.cost(){{ '}' }}`);
// "Simple coffee, milk, sugar, whipped cream - $3.4"

Decorator per Logging e Caching

Esempio pratico: aggiungere logging e caching a un service:

Service Decorators
// Component interface
interface DataService {{ '{' }}
  fetchData(id: string): Promise<any>;
{{ '}' }}

// Concrete component
class APIDataService implements DataService {{ '{' }}
  async fetchData(id: string): Promise<any> {{ '{' }}
    console.log(`🌐 Fetching data from API for ID: ${{ '{' }}id{{ '}' }}`);
    // Simula API call
    await new Promise(resolve => setTimeout(resolve, 1000));
    return {{ '{' }} id, data: `Data for ${{ '{' }}id{{ '}' }}` {{ '}' }};
  {{ '}' }}
{{ '}' }}

// Logging Decorator
class LoggingDecorator implements DataService {{ '{' }}
  constructor(private service: DataService) {{ '{' }}{{ '}' }}

  async fetchData(id: string): Promise<any> {{ '{' }}
    console.log(`📝 [LOG] Fetching data for: ${{ '{' }}id{{ '}' }}`);
    const startTime = Date.now();

    try {{ '{' }}
      const result = await this.service.fetchData(id);
      const duration = Date.now() - startTime;
      console.log(`📝 [LOG] Success in ${{ '{' }}duration{{ '}' }}ms`);
      return result;
    {{ '}' }} catch (error) {{ '{' }}
      console.log(`📝 [LOG] Error: ${{ '{' }}error{{ '}' }}`);
      throw error;
    {{ '}' }}
  {{ '}' }}
{{ '}' }}

// Caching Decorator
class CachingDecorator implements DataService {{ '{' }}
  private cache: Map<string, any> = new Map();

  constructor(private service: DataService) {{ '{' }}{{ '}' }}

  async fetchData(id: string): Promise<any> {{ '{' }}
    if (this.cache.has(id)) {{ '{' }}
      console.log(`💾 [CACHE] Cache hit for: ${{ '{' }}id{{ '}' }}`);
      return this.cache.get(id);
    {{ '}' }}

    console.log(`💾 [CACHE] Cache miss for: ${{ '{' }}id{{ '}' }}`);
    const result = await this.service.fetchData(id);
    this.cache.set(id, result);
    return result;
  {{ '}' }}
{{ '}' }}

// Retry Decorator
class RetryDecorator implements DataService {{ '{' }}
  constructor(
    private service: DataService,
    private maxRetries: number = 3
  ) {{ '{' }}{{ '}' }}

  async fetchData(id: string): Promise<any> {{ '{' }}
    let lastError;

    for (let attempt = 1; attempt <= this.maxRetries; attempt++) {{ '{' }}
      try {{ '{' }}
        console.log(`🔄 [RETRY] Attempt ${{ '{' }}attempt{{ '}' }}/${{ '{' }}this.maxRetries{{ '}' }}`);
        return await this.service.fetchData(id);
      {{ '}' }} catch (error) {{ '{' }}
        lastError = error;
        if (attempt < this.maxRetries) {{ '{' }}
          console.log(`🔄 [RETRY] Failed, retrying...`);
          await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
        {{ '}' }}
      {{ '}' }}
    {{ '}' }}

    throw lastError;
  {{ '}' }}
{{ '}' }}

// Utilizzo: componi decorators
let service: DataService = new APIDataService();

// Aggiungi logging
service = new LoggingDecorator(service);

// Aggiungi caching
service = new CachingDecorator(service);

// Aggiungi retry
service = new RetryDecorator(service, 3);

// Prima chiamata: cache miss, API call
await service.fetchData("123");
// 📝 [LOG] Fetching data for: 123
// 💾 [CACHE] Cache miss for: 123
// 🔄 [RETRY] Attempt 1/3
// 🌐 Fetching data from API for ID: 123
// 📝 [LOG] Success in 1002ms

// Seconda chiamata: cache hit, nessuna API call
await service.fetchData("123");
// 📝 [LOG] Fetching data for: 123
// 💾 [CACHE] Cache hit for: 123
// 📝 [LOG] Success in 2ms

Facade Pattern

Il Facade pattern fornisce un'interfaccia unificata e semplificata a un insieme di interfacce in un sottosistema. Rende il sottosistema più facile da usare.

TypeScript - Facade Base
// Sottosistema complesso: Home Theater
class Amplifier {{ '{' }}
  on(): void {{ '{' }} console.log("🔊 Amplifier ON"); {{ '}' }}
  setVolume(level: number): void {{ '{' }} console.log(`🔊 Volume set to ${{ '{' }}level{{ '}' }}`); {{ '}' }}
  off(): void {{ '{' }} console.log("🔊 Amplifier OFF"); {{ '}' }}
{{ '}' }}

class DvdPlayer {{ '{' }}
  on(): void {{ '{' }} console.log("📀 DVD Player ON"); {{ '}' }}
  play(movie: string): void {{ '{' }} console.log(`📀 Playing: ${{ '{' }}movie{{ '}' }}`); {{ '}' }}
  stop(): void {{ '{' }} console.log("📀 Stopped"); {{ '}' }}
  off(): void {{ '{' }} console.log("📀 DVD Player OFF"); {{ '}' }}
{{ '}' }}

class Projector {{ '{' }}
  on(): void {{ '{' }} console.log("📽️ Projector ON"); {{ '}' }}
  setInput(source: string): void {{ '{' }} console.log(`📽️ Input: ${{ '{' }}source{{ '}' }}`); {{ '}' }}
  off(): void {{ '{' }} console.log("📽️ Projector OFF"); {{ '}' }}
{{ '}' }}

class Lights {{ '{' }}
  dim(level: number): void {{ '{' }} console.log(`💡 Lights dimmed to ${{ '{' }}level{{ '}' }}%`); {{ '}' }}
  on(): void {{ '{' }} console.log("💡 Lights ON"); {{ '}' }}
{{ '}' }}

// ❌ Senza Facade: cliente deve gestire tutto
function watchMovieWithoutFacade(movie: string): void {{ '{' }}
  const amp = new Amplifier();
  const dvd = new DvdPlayer();
  const projector = new Projector();
  const lights = new Lights();

  lights.dim(10);
  projector.on();
  projector.setInput("DVD");
  amp.on();
  amp.setVolume(5);
  dvd.on();
  dvd.play(movie);
  // ... complessità elevata!
{{ '}' }}

// ✅ Con Facade: interfaccia semplificata
class HomeTheaterFacade {{ '{' }}
  constructor(
    private amp: Amplifier,
    private dvd: DvdPlayer,
    private projector: Projector,
    private lights: Lights
  ) {{ '{' }}{{ '}' }}

  watchMovie(movie: string): void {{ '{' }}
    console.log("🎬 Getting ready to watch a movie...\n");
    this.lights.dim(10);
    this.projector.on();
    this.projector.setInput("DVD");
    this.amp.on();
    this.amp.setVolume(5);
    this.dvd.on();
    this.dvd.play(movie);
    console.log("\n🍿 Enjoy the movie!");
  {{ '}' }}

  endMovie(): void {{ '{' }}
    console.log("\n👋 Shutting down movie theater...\n");
    this.dvd.stop();
    this.dvd.off();
    this.amp.off();
    this.projector.off();
    this.lights.on();
    console.log("\n✅ Movie theater shut down");
  {{ '}' }}
{{ '}' }}

// Utilizzo: semplice e chiaro
const homeTheater = new HomeTheaterFacade(
  new Amplifier(),
  new DvdPlayer(),
  new Projector(),
  new Lights()
);

homeTheater.watchMovie("Inception");
// 🎬 Getting ready to watch a movie...
// 💡 Lights dimmed to 10%
// 📽️ Projector ON
// 📽️ Input: DVD
// 🔊 Amplifier ON
// 🔊 Volume set to 5
// 📀 DVD Player ON
// 📀 Playing: Inception
// 🍿 Enjoy the movie!

homeTheater.endMovie();
// 👋 Shutting down movie theater...
// 📀 Stopped
// 📀 DVD Player OFF
// 🔊 Amplifier OFF
// 📽️ Projector OFF
// 💡 Lights ON
// ✅ Movie theater shut down

Facade per Database Operations

Esempio pratico: semplificare operazioni database complesse:

Database Facade
// Sottosistema complesso
class ConnectionPool {{ '{' }}
  getConnection(): void {{ '{' }} console.log("🔌 Getting connection from pool"); {{ '}' }}
  releaseConnection(): void {{ '{' }} console.log("🔌 Releasing connection"); {{ '}' }}
{{ '}' }}

class QueryBuilder {{ '{' }}
  buildSelectQuery(table: string, conditions: any): string {{ '{' }}
    console.log(`🔨 Building SELECT query for ${{ '{' }}table{{ '}' }}`);
    return `SELECT * FROM ${{ '{' }}table{{ '}' }} WHERE ...`;
  {{ '}' }}

  buildInsertQuery(table: string, data: any): string {{ '{' }}
    console.log(`🔨 Building INSERT query for ${{ '{' }}table{{ '}' }}`);
    return `INSERT INTO ${{ '{' }}table{{ '}' }} ...`;
  {{ '}' }}
{{ '}' }}

class TransactionManager {{ '{' }}
  begin(): void {{ '{' }} console.log("⚙️ Transaction started"); {{ '}' }}
  commit(): void {{ '{' }} console.log("✅ Transaction committed"); {{ '}' }}
  rollback(): void {{ '{' }} console.log("↩️ Transaction rolled back"); {{ '}' }}
{{ '}' }}

class CacheManager {{ '{' }}
  get(key: string): any {{ '{' }}
    console.log(`💾 Cache lookup: ${{ '{' }}key{{ '}' }}`);
    return null;
  {{ '}' }}

  set(key: string, value: any): void {{ '{' }}
    console.log(`💾 Cache set: ${{ '{' }}key{{ '}' }}`);
  {{ '}' }}
{{ '}' }}

// Facade: semplifica tutto
class DatabaseFacade {{ '{' }}
  private pool = new ConnectionPool();
  private queryBuilder = new QueryBuilder();
  private txManager = new TransactionManager();
  private cache = new CacheManager();

  async findUser(id: string): Promise<any> {{ '{' }}
    // Controlla cache
    const cached = this.cache.get(`user:${{ '{' }}id{{ '}' }}`);
    if (cached) return cached;

    // Esegui query
    this.pool.getConnection();
    const query = this.queryBuilder.buildSelectQuery('users', {{ '{' }} id {{ '}' }});
    console.log(`🔍 Executing: ${{ '{' }}query{{ '}' }}`);

    const user = {{ '{' }} id, name: 'Alice' {{ '}' }}; // Simula risultato

    this.cache.set(`user:${{ '{' }}id{{ '}' }}`, user);
    this.pool.releaseConnection();

    return user;
  {{ '}' }}

  async createUser(userData: any): Promise<void> {{ '{' }}
    this.pool.getConnection();
    this.txManager.begin();

    try {{ '{' }}
      const query = this.queryBuilder.buildInsertQuery('users', userData);
      console.log(`➕ Executing: ${{ '{' }}query{{ '}' }}`);

      this.txManager.commit();
      this.cache.set(`user:${{ '{' }}userData.id{{ '}' }}`, userData);
    {{ '}' }} catch (error) {{ '{' }}
      this.txManager.rollback();
      throw error;
    {{ '}' }} finally {{ '{' }}
      this.pool.releaseConnection();
    {{ '}' }}
  {{ '}' }}
{{ '}' }}

// Utilizzo: semplice e pulito
const db = new DatabaseFacade();

await db.findUser("123");
// 💾 Cache lookup: user:123
// 🔌 Getting connection from pool
// 🔨 Building SELECT query for users
// 🔍 Executing: SELECT * FROM users WHERE ...
// 💾 Cache set: user:123
// 🔌 Releasing connection

await db.createUser({{ '{' }} id: "456", name: "Bob" {{ '}' }});
// 🔌 Getting connection from pool
// ⚙️ Transaction started
// 🔨 Building INSERT query for users
// ➕ Executing: INSERT INTO users ...
// ✅ Transaction committed
// 💾 Cache set: user:456
// 🔌 Releasing connection

Quando Usare Ogni Pattern

✅ Usa Adapter quando:

  • Vuoi usare classi esistenti ma le loro interfacce sono incompatibili
  • Integrare librerie di terze parti (API payment, logging, ecc.)
  • Migrare da un sistema vecchio a uno nuovo gradualmente
  • Uniform interface per componenti eterogenei

✅ Usa Decorator quando:

  • Vuoi aggiungere responsabilità dinamicamente senza ereditarietà
  • L'ereditarietà non è pratica (troppe combinazioni)
  • Cross-cutting concerns: logging, caching, validation
  • Composizione flessibile di funzionalità

✅ Usa Facade quando:

  • Vuoi semplificare un sistema complesso
  • Ridurre dipendenze tra client e sottosistema
  • Definire entry point per layer (database, API)
  • Nascondere complessità interna

Tutti e Tre i Pattern Combinati

E-Commerce System Completo
// ADAPTER: Adatta API esterne
interface ShippingService {{ '{' }}
  calculateShipping(weight: number): number;
{{ '}' }}

class FedExAPI {{ '{' }}
  computeCost(kg: number): number {{ '{' }} return kg * 5; {{ '}' }}
{{ '}' }}

class FedExAdapter implements ShippingService {{ '{' }}
  private fedex = new FedExAPI();

  calculateShipping(weight: number): number {{ '{' }}
    return this.fedex.computeCost(weight);
  {{ '}' }}
{{ '}' }}

// DECORATOR: Aggiungi funzionalità
class DiscountDecorator implements ShippingService {{ '{' }}
  constructor(
    private service: ShippingService,
    private discount: number
  ) {{ '{' }}{{ '}' }}

  calculateShipping(weight: number): number {{ '{' }}
    const cost = this.service.calculateShipping(weight);
    return cost * (1 - this.discount);
  {{ '}' }}
{{ '}' }}

class InsuranceDecorator implements ShippingService {{ '{' }}
  constructor(private service: ShippingService) {{ '{' }}{{ '}' }}

  calculateShipping(weight: number): number {{ '{' }}
    const cost = this.service.calculateShipping(weight);
    return cost + 2.99; // Assicurazione fissa
  {{ '}' }}
{{ '}' }}

// FACADE: Semplifica checkout
class PaymentGateway {{ '{' }}
  charge(amount: number): void {{ '{' }} console.log(`💳 Charged $${{ '{' }}amount{{ '}' }}`); {{ '}' }}
{{ '}' }}

class InventorySystem {{ '{' }}
  reserve(productId: string): void {{ '{' }} console.log(`📦 Reserved product ${{ '{' }}productId{{ '}' }}`); {{ '}' }}
{{ '}' }}

class EmailService {{ '{' }}
  sendConfirmation(email: string): void {{ '{' }} console.log(`📧 Confirmation sent to ${{ '{' }}email{{ '}' }}`); {{ '}' }}
{{ '}' }}

class CheckoutFacade {{ '{' }}
  constructor(
    private shippingService: ShippingService,
    private payment: PaymentGateway,
    private inventory: InventorySystem,
    private email: EmailService
  ) {{ '{' }}{{ '}' }}

  processOrder(productId: string, weight: number, userEmail: string): void {{ '{' }}
    console.log("🛒 Processing order...\n");

    // Calcola spedizione (con adapter + decorators)
    const shippingCost = this.shippingService.calculateShipping(weight);
    console.log(`📮 Shipping: $${{ '{' }}shippingCost.toFixed(2){{ '}' }}`);

    // Processa pagamento
    const total = 50 + shippingCost; // 50 = prezzo prodotto
    this.payment.charge(total);

    // Riserva inventario
    this.inventory.reserve(productId);

    // Invia conferma
    this.email.sendConfirmation(userEmail);

    console.log("\n✅ Order completed!");
  {{ '}' }}
{{ '}' }}

// Utilizzo combinato
let shipping: ShippingService = new FedExAdapter();
shipping = new DiscountDecorator(shipping, 0.1); // 10% sconto
shipping = new InsuranceDecorator(shipping); // Assicurazione

const checkout = new CheckoutFacade(
  shipping,
  new PaymentGateway(),
  new InventorySystem(),
  new EmailService()
);

checkout.processOrder("PROD123", 2.5, "user@example.com");
// 🛒 Processing order...
// 📮 Shipping: $14.24  (2.5kg * 5 * 0.9 + 2.99)
// 💳 Charged $64.24
// 📦 Reserved product PROD123
// 📧 Confirmation sent to user@example.com
// ✅ Order completed!

Conclusione

Questi tre pattern strutturali risolvono problemi complementari. Adapter integra sistemi incompatibili, Decorator estende funzionalità dinamicamente senza ereditarietà, e Facade nasconde complessità fornendo interfacce semplici. Usali insieme per architetture flessibili e manutenibili.

🎯 Punti Chiave

  • Adapter = compatibilità tra interfacce incompatibili
  • Decorator = aggiungi funzionalità dinamicamente (composizione > ereditarietà)
  • Facade = semplifica sistemi complessi con interfaccia unificata
  • Adapter per integrare API esterne
  • Decorator per cross-cutting concerns (logging, caching)
  • Facade per nascondere complessità e ridurre accoppiamento

Domande frequenti

Qual è il principale scopo dell'Adapter Pattern?

L'Adapter pattern converte l'interfaccia di una classe in un'altra interfaccia che i client si aspettano, permettendo a classi con interfacce incompatibili di collaborare.

Qual è la principale differenza tra Decorator e ereditarietà?

Il Decorator pattern attacca responsabilità aggiuntive a un oggetto dinamicamente, fornendo un'alternativa flessibile all'ereditarietà per estendere funzionalità.

Qual è il vantaggio principale del Facade Pattern?

Il Facade pattern fornisce un'interfaccia unificata e semplificata a un insieme di interfacce in un sottosistema, rendendo il sottosistema più facile da usare.

Perché è importante combinare i tre pattern strutturali?

I tre pattern strutturali risolvono problemi complementari e possono essere utilizzati insieme per creare architetture flessibili e manutenibili.

Fonti e approfondimenti

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti