La mia opinione sull'argomento.
Tutti e quattro i motivi hanno molto in comune, tutti e quattro sono talvolta chiamati in modo informale wrapper o modelli di wrapper. Tutti usano la composizione, avvolgendo il soggetto e delegando l'esecuzione al soggetto ad un certo punto, mappando una chiamata di metodo a un'altra. Risparmiano al cliente la necessità di dover costruire un oggetto diverso e copiarlo su tutti i dati rilevanti. Se usati con saggezza, risparmiano memoria e processore.
Promuovendo l'accoppiamento libero rendono il codice una volta stabile meno esposto alle inevitabili modifiche e meglio leggibile per gli altri sviluppatori.
Adattatore
L'adattatore adatta il soggetto (adattato) a un'interfaccia diversa. In questo modo possiamo aggiungere l'oggetto da collocare in una raccolta di tipi nominalmente diversi.
L'adapter espone al client solo metodi pertinenti, può limitare tutti gli altri, rivelando gli intenti di utilizzo per contesti particolari, come ad esempio l'adattamento della libreria esterna, rendendolo meno generale e più focalizzato sulle esigenze della nostra applicazione. Gli adattatori aumentano la leggibilità e l'auto-descrizione del nostro codice.
Gli adattatori proteggono una squadra dal codice volatile di altre squadre; uno strumento salvavita quando si tratta di squadre offshore ;-)
Lo scopo meno menzionato è quello di impedire alla classe soggetto di eccedere le annotazioni. Con così tanti framework basati su annotazioni, questo diventa un utilizzo più importante che mai.
L'adapter consente di aggirare la limitazione Java della sola eredità. Può combinare più adattati in un'unica busta dando l'impressione di ereditarietà multipla.
Per quanto riguarda il codice, l'adattatore è "sottile". Non dovrebbe aggiungere molto codice alla classe degli adattati, oltre a chiamare semplicemente il metodo degli adattati e occasionali conversioni di dati necessarie per effettuare tali chiamate.
Non ci sono molti buoni esempi di adattatori in JDK o nelle librerie di base. Gli sviluppatori di applicazioni creano adattatori, per adattare le librerie alle interfacce specifiche dell'applicazione.
Decoratore
Decorator non solo delega, non solo mappa un metodo su un altro, fa di più, modifica il comportamento di alcuni metodi del soggetto, può decidere di non chiamare affatto il metodo del soggetto, delegare a un altro oggetto, un oggetto helper.
I decoratori in genere aggiungono funzionalità (in modo trasparente) all'oggetto avvolto come registrazione, crittografia, formattazione o compressione sull'oggetto. Questa nuova funzionalità può portare molto nuovo codice. Quindi, i decoratori di solito sono molto più "grassi" degli adattatori.
Il decoratore deve essere una sottoclasse dell'interfaccia del soggetto. Possono essere usati in modo trasparente invece dei suoi soggetti. Vedi BufferedOutputStream, è ancora OutputStream e può essere usato come tale. Questa è una grande differenza tecnica rispetto agli adattatori.
Esempi di libri di testo dell'intera famiglia di decoratori si trovano facilmente in JDK - Java IO. Tutte le classi come BufferedOutputStream , FilterOutputStream e ObjectOutputStream sono decoratori di OutputStream . Possono essere a strati di cipolla, dove un solo decoratore viene nuovamente decorato, aggiungendo più funzionalità.
delega
Il proxy non è un wrapper tipico. L'oggetto spostato, l'oggetto del proxy, potrebbe non esistere al momento della creazione del proxy. Il proxy spesso lo crea internamente. Può essere un oggetto pesante creato su richiesta, oppure è un oggetto remoto in JVM diverso o nodo di rete diverso e persino un oggetto non Java, un componente nel codice nativo. Non deve affatto avvolgere o delegare a un altro oggetto.
Gli esempi più tipici sono proxy remoti, inizializzatori di oggetti pesanti e proxy di accesso.
Proxy remoto: l'oggetto si trova su un server remoto, su un altro JVM o anche su un sistema non Java. Il proxy traduce le chiamate di metodo a chiamate RMI / REST / SOAP o qualsiasi altra cosa sia necessaria, proteggendo il cliente dall'esposizione alla tecnologia sottostante.
Proxy di caricamento lento: inizializza completamente l'oggetto solo al primo utilizzo o al primo utilizzo intensivo.
Proxy d'accesso: controlla l'accesso al soggetto.
Facciata
La facciata è strettamente associata al principio di progettazione della conoscenza minima (Legge di Demetra). La facciata è molto simile all'adattatore. Si avvolgono entrambi, entrambi mappano un oggetto su un altro, ma differiscono nell'intento. La facciata appiattisce la struttura complessa di un soggetto, il grafico di un oggetto complesso, semplificando l'accesso a una struttura complessa.
La facciata avvolge una struttura complessa, fornendo un'interfaccia piatta ad essa. Ciò impedisce che l'oggetto client venga esposto alle relazioni interne nella struttura del soggetto, promuovendo quindi l'accoppiamento libero.
ponte
Variante più complessa del modello di adattatore in cui non solo l'implementazione varia ma anche l'astrazione. Aggiunge un'altra indiretta alla delegazione. La delegazione extra è il ponte. Disaccoppia l'adattatore anche dall'interfaccia adattante. Aumenta la complessità più di ogni altro modello di avvolgimento, quindi applica con cura.
Differenze nei costruttori
Le differenze di modello sono evidenti anche quando si osservano i loro costruttori.
Il proxy non sta avvolgendo un oggetto esistente. Non c'è argomento nel costruttore.
Decoratore e Adattatore avvolgono oggetti già esistenti, e questo è in genere
fornito nel costruttore.
Il costruttore di facciate prende l'elemento radice di un intero oggetto grafico, altrimenti sembra uguale all'adapter.
Esempio di vita reale - Marshalling Adapter JAXB . Lo scopo di questo adattatore è la mappatura di una semplice classe piatta su una struttura più complessa richiesta esternamente e per prevenire la classe soggetto "inquinante" con annotazioni eccessive.