"Hei venn!"

"Hei, Bilaabo!"

"Vi har fortsatt litt tid igjen, så jeg skal fortelle deg om tre mønstre til."

"Tre til? Hvor mange er det i alt?"

"Det er for tiden dusinvis av populære mønstre, men antallet «vellykkede løsninger» er ubegrenset."

"Jeg skjønner. Så jeg må lære flere dusin mønstre?"

"Før du har ekte programmeringserfaring, vil de ikke gi deg mye."

"Du bør få litt mer erfaring, og deretter, om et år, gå tilbake til dette emnet og prøve å forstå dem dypere. Minst et par dusin av de mest populære designmønstrene."

«Det er synd å ikke bruke andres erfaring og i stedet finne på noe for 110. gang».

"Jeg er enig."

"Så la oss begynne."

Adapter (eller omslag) mønster

Mønstre: Adapter, Proxy, Bridge - 1

"Se for deg at du kommer til Kina og finner ut at stikkontaktene følger en annen standard. Hullene er ikke runde, men flate. I dette tilfellet trenger du en adapter."

"Noe lignende kan også skje i programmering. Klasser opererer på lignende, men forskjellige grensesnitt. Så vi må lage en adapter mellom dem."

"Slik ser det ut:"

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

interface TotalTime
{
 int getTotalSeconds();
}

"Anta at vi har to grensesnitt: Time  og  TotalTime ."

"Time-grensesnittet lar deg få gjeldende tid ved å bruke metodene getSeconds (),  getMinutes () og  getHours ().

" TotalTime- grensesnittet lar deg få antall sekunder som har gått fra midnatt til nåværende øyeblikk."

"Hva skal vi gjøre hvis vi har et TotalTime- objekt, men vi trenger et Time- objekt eller omvendt?"

"Vi kan skrive adapterklasser for dette. For eksempel:"

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

"Og en adapter i den andre retningen:"

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

"Ah. Jeg liker det. Men er det noen eksempler?"

"Selvfølgelig! Som et eksempel er InputStreamReader en klassisk adapter. Den konverterer en InputStream til en Reader."

"Noen ganger kalles dette mønsteret også en wrapper, fordi den nye klassen 'pakker inn' et annet objekt."

"Du kan lese noen andre interessante ting her ."

Proxy mønster

"Proxy-mønsteret ligner litt på wrapper-mønsteret. Men formålet er ikke å konvertere grensesnitt, men å kontrollere tilgangen til det originale objektet som er lagret inne i proxy-klassen. Dessuten har både den opprinnelige klassen og proxy-en vanligvis samme grensesnitt, som gjør det lettere å erstatte et objekt av den opprinnelige klassen med et proxy-objekt."

"For eksempel:"

Grensesnittet til den virkelige klassen
interface Bank
{
 public void setUserMoney(User user, double money);
 public int getUserMoney(User user);
}
Implementering av den opprinnelige klassen
class CitiBank implements Bank
{
 public void setUserMoney(User user, double money)
 {
  UserDAO.updateMoney(user, money);
 }

 public int getUserMoney(User user)
 {
  return UserDAO.getMoney(user);
 }
}
Implementering av proxy-klassen
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);
 }
}

"I eksemplet ovenfor beskrev vi Bank- grensesnittet og CitiBank- klassen, en implementering av dette grensesnittet."

"Grensesnittet lar deg få eller endre en brukers kontosaldo."

Og så opprettet vi BankSecurityProxy , som også implementerer Bank- grensesnittet og lagrer en referanse til et annet Bank-grensesnitt. Metodene i denne klassen sjekker om brukeren er kontoeier eller banksjef. Hvis det ikke er det, blir et sikkerhetsunntak kastet."

"Slik fungerer det i praksis:"

Kode uten sikkerhetskontroller:
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank.setUserMoney(user, 1000000);
Kode med sikkerhetssjekker:
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank = new BankSecurityProxy(bank);
bank.setUserMoney(user, 1000000);

"I det første eksemplet lager vi et bankobjekt og kaller dets setUserMoney- metoden.

"I det andre eksemplet pakker vi inn det originale bankobjektet i et BankSecurityProxy- objekt. De har samme grensesnitt, så påfølgende kode fortsetter å fungere som den gjorde. Men nå vil en sikkerhetssjekk utføres hver gang en metode kalles."

"Kul!"

"Jepp. Du kan ha mange slike fullmakter. Du kan for eksempel legge til en annen proxy som sjekker om kontosaldoen er for stor. Banksjefen kan bestemme seg for å sette mye penger på sin egen konto og rømme til Cuba med midlene ."

"I tillegg... Opprettelsen av alle disse kjedene med objekter kan settes inn i en BankFactory- klasse, hvor du kan aktivere/deaktivere de du trenger."

" BufferedReader fungerer etter et lignende prinsipp. Det er en Reader , men det utfører ekstra arbeid."

"Denne tilnærmingen lar deg «sette sammen» et objekt med den nødvendige funksjonaliteten fra forskjellige «stykker».

"Å, jeg glemte det nesten. Fullmaktsfunksjoner brukes mye mer enn det jeg nettopp viste deg. Du kan lese om andre bruksområder her ."

Bromønster

Mønstre: Adapter, Proxy, Bridge - 2

"Noen ganger er det nødvendig å endre funksjonaliteten til et objekt betydelig. Anta for eksempel at du har et spill med en eselkarakter som senere blir omgjort til en drage av en mage. Dragen har en helt annen oppførsel og egenskaper, men det er samme gjenstand!"

"Kan vi ikke bare lage et nytt objekt og bli ferdig med det?"

"Ikke alltid. Anta at eselet ditt er venn med en haug med karakterer, eller kanskje det var under påvirkning av flere trollformler, eller det var involvert i visse oppdrag. Med andre ord kan objektet allerede være i bruk mange steder - og koblet til mange andre objekter. Så i dette tilfellet er det ikke et alternativ å bare lage et nytt objekt."

"Vel, hva kan gjøres da?"

"Bromønsteret er en av de mest vellykkede løsningene."

"Dette mønsteret innebærer å dele et objekt i to objekter: et «grensesnittobjekt» og et «implementeringsobjekt».

"Hva er forskjellen mellom grensesnittet og klassen som implementerer det?"

"Med et grensesnitt og en klasse ender vi opp med ett objekt. Men her – vi har to. Se på dette eksemplet:"

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

"Og så kan du deklarere flere underklasser av UserImpl, for eksempel UserDonkey (esel) og UserDragon (drage)."

"Allikevel forstår jeg ikke helt hvordan dette vil fungere."

"Vel, noe sånt som dette:"

Eksempel
class User
{
 private UserImpl realUser;

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

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

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

"Så det er noe sånt som en proxy."

"Ja, men i en proxy kan hovedobjektet lagres et sted separat, og koden fungerer med proxyer i stedet. Her sier vi at alle jobber med hovedobjektet, men dets deler endres internt."

"Ah. Takk. Vil du gi meg en lenke for å lese mer om det?"

"Selvfølgelig, Amigo, min venn. Her går du: Bromønster ."