"Salut prietene!"

— Bună, Bilaabo!

„Avem încă ceva timp, așa că vă voi spune despre încă trei modele”.

— Încă trei? Câţi sunt în total?

„În prezent, există zeci de modele populare, dar numărul de „soluții de succes” este nelimitat.”

— Înțeleg. Deci trebuie să învăț câteva zeci de modele?

„Până nu ai experiență reală în programare, nu îți vor oferi prea multe.”

„Ar fi bine să obțineți puțin mai multă experiență și apoi, într-un an, să reveniți la acest subiect și să încercați să le înțelegeți mai profund. Cel puțin câteva zeci dintre cele mai populare modele de design.”

„Este un păcat să nu folosești experiența altcuiva și să inventezi ceva pentru a 110-a oară.”

"Sunt de acord."

— Atunci să începem.

Model de adaptor (sau înveliș).

Modele: Adaptor, Proxy, Bridge - 1

"Imaginați-vă că veniți în China și descoperiți că prizele electrice respectă un standard diferit. Găurile nu sunt rotunde, ci plate. În acest caz, veți avea nevoie de un adaptor."

"Ceva similar se poate întâmpla și în programare. Clasele funcționează pe interfețe similare, dar diferite. Așa că trebuie să facem un adaptor între ele."

„Așa arată:”

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

interface TotalTime
{
 int getTotalSeconds();
}

„Să presupunem că avem două interfețe: Time  și  TotalTime ”.

„Interfața Time vă permite să obțineți ora curentă folosind metodele getSeconds (),  getMinutes () și  getHours ()”.

„ Interfața TotalTime vă permite să obțineți numărul de secunde care au trecut de la miezul nopții până la momentul actual.”

„Ce ar trebui să facem dacă avem un obiect TotalTime , dar avem nevoie de un obiect Time sau invers?”

„Putem scrie clase de adaptoare pentru asta. De exemplu:”

Exemplu
 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
 }
}
 
Utilizare
TotalTime totalTime = TimeManager.getCurrentTime();
Time time = new TotalTimeAdapter(totalTime);
System.out.println(time.getHours() + " : " + time.getMinutes () + " : " +time.getSeconds());

„Și un adaptor în cealaltă direcție:”

Exemplu
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();
 }
}
Utilizare
Time time = new Time();
TotalTime totalTime = new TimeAdapter(time);
System.out.println(time.getTotalSeconds());

"Ah. Îmi place. Dar există exemple?"

"Desigur! Ca exemplu, InputStreamReader este un adaptor clasic. Acesta convertește un InputStream într-un Reader."

„Uneori, acest model este numit și înveliș, deoarece noua clasă „înfășoară” un alt obiect.”

„Puteți citi și alte lucruri interesante aici .”

Model proxy

„Șablonul proxy este oarecum similar cu modelul wrapper. Dar scopul său nu este de a converti interfețele, ci de a controla accesul la obiectul original stocat în interiorul clasei proxy. Mai mult, atât clasa originală, cât și proxy-ul au de obicei aceeași interfață, ceea ce face mai ușoară înlocuirea unui obiect din clasa originală cu un obiect proxy."

"De exemplu:"

Interfața clasei reale
interface Bank
{
 public void setUserMoney(User user, double money);
 public int getUserMoney(User user);
}
Implementarea clasei 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);
 }
}
Implementarea clasei 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);
 }
}

„În exemplul de mai sus, am descris interfața Bank și clasa CitiBank , o implementare a acestei interfețe.”

„Interfața vă permite să obțineți sau să modificați soldul contului unui utilizator.”

Și apoi am creat BankSecurityProxy , care implementează și interfața Bank și stochează o referință la o altă interfață Bank. Metodele acestei clase verifică dacă utilizatorul este proprietarul contului sau un manager de bancă. Dacă nu este, atunci se aruncă o excepție de securitate.”

„Iată cum funcționează în practică:”

Cod fără verificări de securitate:
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank.setUserMoney(user, 1000000);
Cod cu controale de securitate:
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank = new BankSecurityProxy(bank);
bank.setUserMoney(user, 1000000);

„În primul exemplu, creăm un obiect bancar și îi apelăm metoda setUserMoney .

„În cel de-al doilea exemplu, împachetăm obiectul bancar inițial într-un obiect BankSecurityProxy . Au aceeași interfață, astfel încât codul ulterior continuă să funcționeze așa cum a făcut. Dar acum va fi efectuată o verificare de securitate de fiecare dată când este apelată o metodă.”

"Misto!"

„Da. Puteți avea multe astfel de proxy. De exemplu, puteți adăuga un alt proxy care verifică dacă soldul contului este prea mare. Managerul băncii ar putea decide să pună mulți bani în cont propriu și să fugă în Cuba cu fondurile. ."

„Mai mult... Crearea tuturor acestor lanțuri de obiecte poate fi pusă într-o clasă BankFactory , unde le poți activa/dezactiva pe cele de care ai nevoie.”

BufferedReader funcționează folosind un principiu similar. Este un Reader , dar efectuează lucrări suplimentare.”

„Această abordare vă permite să „asamblați” un obiect cu funcționalitatea necesară din diverse „piese”.”

"Oh, aproape că am uitat. Proxy-urile sunt folosite mult mai pe scară largă decât ceea ce tocmai ți-am arătat. Puteți citi despre alte utilizări aici ."

Model de punte

Modele: Adaptor, Proxy, Bridge - 2

„Uneori, pe măsură ce un program rulează, este necesar să se schimbe semnificativ funcționalitatea unui obiect. De exemplu, să presupunem că aveți un joc cu un personaj măgar care este transformat ulterior într-un dragon de către un mag. Dragonul are un comportament și proprietăți complet diferite, dar este același obiect!"

„Nu putem să creăm un obiect nou și să terminăm cu el?”

„Nu întotdeauna. Să presupunem că măgarul tău este prieten cu o grămadă de personaje, sau poate că a fost sub influența mai multor vrăji sau că a fost implicat în anumite misiuni. Cu alte cuvinte, obiectul poate fi deja folosit în multe locuri — și conectat la o mulțime de alte obiecte. Deci, în acest caz, nu este o opțiune de a crea pur și simplu un obiect nou."

— Ei bine, ce se poate face atunci?

„Modelul Bridge este una dintre cele mai de succes soluții”.

„Acest model presupune împărțirea unui obiect în două obiecte: un „obiect de interfață” și un „obiect de implementare”.”

„Care este diferența dintre interfață și clasa care o implementează?”

„Cu o interfață și o clasă, ajungem cu un singur obiect. Dar aici – avem două. Uită-te la acest exemplu:”

Exemplu
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()
 {
 }
}

„Și apoi puteți declara mai multe subclase de UserImpl, de exemplu UserDonkey (măgar) și UserDragon (dragon).”

— Cu toate acestea, nu prea înțeleg cum va funcționa asta.

"Ei bine, ceva de genul asta:"

Exemplu
class User
{
 private UserImpl realUser;

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

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

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

„Deci este ceva ca un proxy”.

„Da, dar într-un proxy obiectul principal ar putea fi stocat undeva separat, iar codul funcționează cu proxy-uri. Aici spunem că toată lumea lucrează cu obiectul principal, dar părțile sale se schimbă intern.”

"Ah. Mulțumesc. Îmi dai un link pentru a citi mai multe despre asta?"

"Desigur, Amigo, prietenul meu. Iată: modelul de pod ."