Oggi parliamo del pattern bridge, un altro dei pattern classici del catalogo Gang of four.
E’ un pattern strutturale che separa “la definizione” di un servizio dal “come” questo servizio funziona realmente. Diventa tanto più utile quanto più numerose sono le varianti del servizio stesso.

Parliamo in concreto.
Mi sono trovato ad implementarlo dovendo realizzare un server web in grado di gestire delle chiamate remote in arrivo da terze parti differenti.
Ciascuna di queste terze parti comunicava con il mio server per scambiare delle informazioni legate ad autorizzazioni e abilitazioni. Il servizio gestito era il medesimo per tutti ma ciascuna di questa terze parti lo implementava con parametri, tracciati e risposte differenti pur restando identico lo scopo generale.
Quindi il mio AuthorityServer è in grado di prendere in carico generiche richieste di autorizzazione e blocco ma ogni richiesta può essere diversa in base all’attore che si trova dall’altra parte.

Se fin qui è tutto chiaro allora possiamo andare avanti, adesso entra in gioco il bridge e la situazione si complicherà un tantino.

Mi serve quindi realizzare:

  • delle classi per definire in astratto l’insieme delle operazioni che il mio server di authority è in grado di gestire.
  • delle classi per definire la reale implementazione prevista per ciascuna delle terza parti con cui andrò a integrarmi.

Vediamo quindi le classi principali di cui avrò bisogno:

  • Interfaccia ConcreteAuthorityService: viene implementata da tutte le classi concrete che rappresentano il servizio per una determinata terza parte. Come ogni interfaccia che si rispetti ha lo scopo di tenere generico il mio codice. Questa interfaccia conterrà gli stessi metodi esposti dalla classe AbstractAuthorityService ma avremo l’accortezza di definirli in modo da ricordarci che èarliamo dell’implementazione del servizio e non della sua definizione, quindi:
    • autorizzaImpl(),
    • negaImpl(),
    • accettaImpl()
  • Classi specifiche Type1AuthorityService, Type2AuthorityService che implementano l’interfaccia ConcreteAuthorityService e ovviamente all’interno dei metodi vanno a gestire le differenze oggettive tra le varie implementazioni. Ad esempio:
    • nella Type1 potremmo avere 3 parametri previsti nel metodo di autorizzazione (autorizzaImpl) e un tracciato http con parametri in GET.
    • nella Type2 potremmo avere 5 parametri previsti nel metodo di autorizzazione (autorizzaImpl) e un tracciato XML su chiamata http in POST.
  • Classe AbstractAuthorityService: è una classe astratta che contiene esclusivamente:
    • la segnatura dei miei metodi di interfaccia:
      • autorizza(),
      • nega(),
      • accetta()
    • una variabile di classe di tipo ConcreteAuthorityService.
  • Classe AuthorityService: estende la classe AbstractAuthorityService ed è l’unica classe con la quale andrò ad interagire all’interno del mio codice. Il costruttore prende in ingresso un oggetto di tipo ConcreteAuthorityService che chiameremo “serviceImpl”, quindi una ben precisa implementazione del mio servizio. All’interno dei metodi di interfaccia non faremo altro che invocare serviceImpl.autorizzaImpl(), serviceImpl.negaImpl() e così via.

Con queste classi in mente, vediamo cosa succede all’interno del nostro codice.
Diciamo di trovarci all’interno della Servlet che prende in carico la chiamata http, non faremo altro che scrivere:

AuthorityService authService = new AuthorityService(new Type1AuthorityService());
authService.autorizza();
authService.nega();

oppure

AuthorityService authService = new AuthorityService(new Type2AuthorityService());
authService.autorizza();
authService.nega();

Come potete vedere il codice utilizzato nei due casi è identico a meno della classe di implementazione del servizio che passiamo al costruttore della nostra classe che definisce il servizio.

Un’ulteriore vantaggio del Bridge è che possiamo anche implementare una classe MockAuthorityService da usare nei casi in cui non abbiamo ancora delle specifiche ben precise. La classe mock ci permette di poter andare avanti con il nostro progetto poichè si comporta esattamente come un’implementazione reale.

Nel momento in cui dobbiamo integrare altre terze parti con il nostro server, non faremo altro che scrivere le classi Type3AuthorityService, Type4AuthorityService e così via.

Design >> Volendo rendere ancora più elegante il tutto, possiamo introdurre una AbstractFactory che restituisce un’implementazione del servizio in base ad un parametro supponiamo una costante numerica, quindi potremmo scrivere:

AuthorityService authService = ServiceFactory.getServiceImpl(TYPE1);

oppure

AuthorityService authService = ServiceFactory.getServiceImpl(TYPE2);

Il vantaggio principale è che la Factory centralizza il metodo di creazione delle risorse e il nostro codice diventa più manutenibile. Dovendo aggiungere una nuova terza parte con una sua specifica implementazione dovremo modificare solo questa classe.

Qui termina la nostra descrizione del pattern bridge. Mi rendo conto che forse la sua applicazione non è banale ma ho cercato di essere il più chiaro possibile fornendo un esempio reale in cui la sua applicazione semplifica realmente la scrittura del codice.