"Ciao amico!"

"Ciao Bilaabo!"

"Abbiamo ancora un po' di tempo, quindi ti parlerò di altri tre modelli."

"Altri tre? Quanti sono in tutto?"

"Attualmente ci sono dozzine di modelli popolari, ma il numero di «soluzioni di successo» è illimitato."

"Capisco. Quindi devo imparare diverse dozzine di schemi?"

"Fino a quando non avrai una vera esperienza di programmazione, non ti daranno molto."

"Faresti meglio a fare un po' più di esperienza e poi, tra un anno, tornare su questo argomento e cercare di capirli più a fondo. Almeno un paio di dozzine dei modelli di design più popolari".

"È un peccato non usare l'esperienza di qualcun altro e invece inventare qualcosa per la 110esima volta".

"Sono d'accordo."

"Allora cominciamo."

Schema adattatore (o involucro).

Schemi: Adattatore, Proxy, Bridge - 1

"Immagina di venire in Cina e scoprire che le prese elettriche seguono uno standard diverso. I fori non sono rotondi, ma piatti. In questo caso, avrai bisogno di un adattatore."

"Qualcosa di simile può accadere anche nella programmazione. Le classi operano su interfacce simili ma diverse. Quindi dobbiamo creare un adattatore tra di loro."

"Ecco come appare:"

Esempio
interface Time
{
 int getSeconds();
 int getMinutes();
 int getHours();
}

interface TotalTime
{
 int getTotalSeconds();
}

"Supponiamo di avere due interfacce: Time  e  TotalTime ."

"L'interfaccia Time ti consente di ottenere l'ora corrente utilizzando i metodi getSeconds (),  getMinutes () e  getHours ()."

"L' interfaccia TotalTime ti consente di ottenere il numero di secondi trascorsi dalla mezzanotte al momento attuale."

"Cosa dovremmo fare se abbiamo un oggetto TotalTime , ma abbiamo bisogno di un oggetto Time o viceversa?"

"Possiamo scrivere classi di adattatori per questo. Ad esempio:"

Esempio
class TotalTimeAdapter implements Time
{
 private TotalTime totalTime;
 public TotalTimeAdapter(TotalTime totalTime)
 {
  this.totalTime = totalTime;
 }

 int getSeconds()
 {
  return totalTime.getTotalSeconds() % 60; // seconds
 }

 int getMinutes()
 {
  return totalTime.getTotalSeconds() / 60; // minutes
 }

 int getHours()
 {
  return totalTime.getTotalSeconds() / (60 * 60); // hours
 }
}
Utilizzo
TotalTime totalTime = TimeManager.getCurrentTime();
Time time = new TotalTimeAdapter(totalTime);
System.out.println(time.getHours() + " : " + time.getMinutes () + " : " +time.getSeconds());

"E un adattatore nell'altra direzione:"

Esempio
class TimeAdapter implements TotalTime
{
 private Time time;
 public TimeAdapter(Time time)
 {
  this.time = time;
 }

 int getTotalSeconds()
 {
  return time.getHours() * 60 * 60 + time.getMinutes() * 60 + time.getSeconds();
 }
}
Utilizzo
Time time = new Time();
TotalTime totalTime = new TimeAdapter(time);
System.out.println(time.getTotalSeconds());

"Ah. Mi piace. Ma ci sono degli esempi?"

"Certo! Ad esempio, InputStreamReader è un adattatore classico. Converte un InputStream in un Reader."

"A volte questo modello è anche chiamato wrapper, perché la nuova classe 'avvolge' un altro oggetto."

"Puoi leggere altre cose interessanti qui ."

Schema proxy

"Il pattern proxy è in qualche modo simile al pattern wrapper. Ma il suo scopo non è convertire le interfacce, ma controllare l'accesso all'oggetto originale memorizzato all'interno della classe proxy. Inoltre, sia la classe originale che il proxy di solito hanno la stessa interfaccia, che semplifica la sostituzione di un oggetto della classe originale con un oggetto proxy."

"Per esempio:"

Interfaccia della classe reale
interface Bank
{
 public void setUserMoney(User user, double money);
 public int getUserMoney(User user);
}
Implementazione della classe originale
class CitiBank implements Bank
{
 public void setUserMoney(User user, double money)
 {
  UserDAO.updateMoney(user, money);
 }

 public int getUserMoney(User user)
 {
  return UserDAO.getMoney(user);
 }
}
Implementazione della classe proxy
class BankSecurityProxy implements Bank
{
 private Bank bank;
 public BankSecurityProxy(Bank bank)
 {
  this.bank = bank;
 }
 public void setUserMoney(User user, double money)
 {
  if (!SecurityManager.authorize(user, BankAccounts.Manager))
  throw new SecurityException("User can’t change money value");

  bank.setUserMoney(user, money);
 }

 public int getUserMoney(User user)
 {
  if (!SecurityManager.authorize(user, BankAccounts.Manager))
  throw new SecurityException("User can’t get money value");

  return bank.getUserMoney(user);
 }
}

"Nell'esempio precedente, abbiamo descritto l' interfaccia Bank e la classe CitiBank , un'implementazione di questa interfaccia."

"L'interfaccia ti consente di ottenere o modificare il saldo del conto di un utente."

E poi abbiamo creato BankSecurityProxy , che implementa anche l' interfaccia Bank e memorizza un riferimento a un'altra interfaccia Bank. I metodi di questa classe controllano se l'utente è il proprietario dell'account o un direttore di banca. In caso contrario, viene generata un'eccezione SecurityException."

"Ecco come funziona in pratica:"

Codice senza controlli di sicurezza:
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank.setUserMoney(user, 1000000);
Codice con controlli di sicurezza:
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank = new BankSecurityProxy(bank);
bank.setUserMoney(user, 1000000);

"Nel primo esempio, creiamo un oggetto banca e chiamiamo il suo metodo setUserMoney .

"Nel secondo esempio, avvolgiamo l'oggetto banca originale in un oggetto BankSecurityProxy . Hanno la stessa interfaccia, quindi il codice successivo continua a funzionare come prima. Ma ora verrà eseguito un controllo di sicurezza ogni volta che viene chiamato un metodo."

"Freddo!"

"Sì. Puoi avere molti di questi proxy. Ad esempio, potresti aggiungere un altro proxy che controlla se il saldo del conto è troppo grande. Il direttore della banca potrebbe decidere di mettere molti soldi sul proprio conto e fuggire a Cuba con i fondi ."

"Inoltre... La creazione di tutte queste catene di oggetti può essere inserita in una classe BankFactory , dove puoi abilitare/disabilitare quelli di cui hai bisogno."

" BufferedReader funziona utilizzando un principio simile. È un Reader , ma svolge un lavoro aggiuntivo."

"Questo approccio ti consente di «assemblare» un oggetto con le funzionalità necessarie da vari «pezzi»."

"Oh, quasi dimenticavo. I proxy sono usati molto più ampiamente di quanto ti ho appena mostrato. Puoi leggere altri usi qui ."

Modello a ponte

Schemi: Adattatore, Proxy, Bridge - 2

"A volte, mentre un programma viene eseguito, è necessario modificare in modo significativo la funzionalità di un oggetto. Ad esempio, supponi di avere un gioco con un personaggio asino che viene successivamente trasformato in un drago da un mago. Il drago ha un comportamento e proprietà completamente diversi, ma è lo stesso oggetto!"

"Non possiamo semplicemente creare un nuovo oggetto e farla finita?"

"Non sempre. Supponiamo che il tuo asino sia amico di un gruppo di personaggi, o forse era sotto l'influenza di diversi incantesimi, o era coinvolto in certe missioni. In altre parole, l'oggetto potrebbe essere già in uso in molti posti... e collegato a molti altri oggetti. Quindi, in questo caso, non è possibile creare semplicemente un nuovo oggetto."

"Bene, cosa si può fare allora?"

"Il modello Bridge è una delle soluzioni di maggior successo."

"Questo schema comporta la divisione di un oggetto in due oggetti: un «oggetto interfaccia» e un «oggetto implementazione»."

"Qual è la differenza tra l'interfaccia e la classe che la implementa?"

"Con un'interfaccia e una classe, finiamo con un oggetto. Ma qui ne abbiamo due. Guarda questo esempio:"

Esempio
class User
{
 private UserImpl realUser;

 public User(UserImpl impl)
 {
  realUser = impl;
 }

 public void run() //Run
 {
  realUser.run();
 }

 public void fly() //Fly
 {
  realUser.fly();
 }
}

class UserImpl
{
 public void run()
 {
 }

 public void fly()
 {
 }
}

"E poi puoi dichiarare diverse sottoclassi di UserImpl, ad esempio UserDonkey (asino) e UserDragon (drago)."

"Tuttavia, non capisco davvero come funzionerà."

"Bene, qualcosa del genere:"

Esempio
class User
{
 private UserImpl realUser;

 public User(UserImpl impl)
 {
  realUser = impl;
 }

 public void transformToDonkey()
 {
  realUser = new UserDonkeyImpl();
 }

 public void transformToDragon()
 {
  realUser = new UserDragonImpl();
 }
}
Come funziona
User user = new User(new UserDonkey()); // Internally, we're a donkey
user.transformToDragon(); // Now we're a dragon internally

"Quindi è qualcosa come un proxy."

"Sì, ma in un proxy l'oggetto principale potrebbe essere memorizzato da qualche parte separatamente, e invece il codice funziona con i proxy. Qui stiamo dicendo che tutti lavorano con l'oggetto principale, ma le sue parti cambiano internamente."

"Ah. Grazie. Mi dai un link per saperne di più?"

"Certo, Amigo, amico mio. Ecco a te: schema del ponte ."