"Hallo vriend!"

"Hallo Bilaabo!"

"We hebben nog wat tijd over, dus ik zal je nog drie patronen vertellen."

'Nog drie? Hoeveel zijn het er in totaal?'

"Er zijn momenteel tientallen populaire patronen, maar het aantal 'succesvolle oplossingen' is onbeperkt."

"Ik begrijp het. Dus ik moet enkele tientallen patronen leren?"

"Totdat je echte programmeerervaring hebt, zullen ze je niet veel geven."

"Je kunt maar beter wat meer ervaring opdoen, en dan over een jaar terugkomen op dit onderwerp en proberen ze dieper te begrijpen. Ten minste een paar dozijn van de meest populaire ontwerppatronen."

"Het is een zonde om de ervaring van iemand anders niet te gebruiken en in plaats daarvan voor de 110e keer iets te verzinnen."

"Daar ben ik het mee eens."

"Laten we dan beginnen."

Adapterpatroon (of wikkelpatroon).

Patronen: adapter, proxy, brug - 1

"Stel je voor dat je naar China komt en ontdekt dat de stopcontacten een andere standaard volgen. De gaten zijn niet rond, maar plat. In dit geval heb je een adapter nodig."

"Iets soortgelijks kan ook gebeuren bij het programmeren. Klassen werken op vergelijkbare maar verschillende interfaces. We moeten dus een adapter tussen hen maken."

"Zo ziet het eruit:"

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

interface TotalTime
{
 int getTotalSeconds();
}

"Stel dat we twee interfaces hebben: Time  en  TotalTime ."

"Met de Time-interface kun je de huidige tijd krijgen met behulp van de getSeconds (),  getMinutes () en  getHours () methoden."

"Met de TotalTime- interface kun je het aantal seconden krijgen dat is verstreken van middernacht tot het huidige moment."

"Wat moeten we doen als we een TotalTime- object hebben, maar we hebben een Time- object nodig of vice versa?"

"Hiervoor kunnen we adapterklassen schrijven. Bijvoorbeeld:"

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

"En een adapter in de andere richting:"

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

"Ah. Ik vind het leuk. Maar zijn er voorbeelden?"

"Natuurlijk! InputStreamReader is bijvoorbeeld een klassieke adapter. Het converteert een InputStream naar een Reader."

"Soms wordt dit patroon ook wel een wrapper genoemd, omdat de nieuwe klasse een ander object 'omhult'."

"Je kunt hier nog wat andere interessante dingen lezen ."

Proxy-patroon

"Het proxy-patroon lijkt enigszins op het wrapper-patroon. Maar het doel is niet om interfaces te converteren, maar om de toegang tot het originele object dat is opgeslagen in de proxy-klasse te controleren. Bovendien hebben zowel de originele klasse als de proxy meestal dezelfde interface, waardoor het gemakkelijker wordt om een ​​object van de oorspronkelijke klasse te vervangen door een proxy-object."

"Bijvoorbeeld:"

Interface van de echte klasse
interface Bank
{
 public void setUserMoney(User user, double money);
 public int getUserMoney(User user);
}
Implementatie van de originele klasse
class CitiBank implements Bank
{
 public void setUserMoney(User user, double money)
 {
  UserDAO.updateMoney(user, money);
 }

 public int getUserMoney(User user)
 {
  return UserDAO.getMoney(user);
 }
}
Implementatie van de proxyklasse
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);
 }
}

"In het bovenstaande voorbeeld hebben we de Bank- interface en de CitiBank- klasse beschreven, een implementatie van deze interface."

"Met de interface kun je het accountsaldo van een gebruiker opvragen of wijzigen."

En toen hebben we BankSecurityProxy gemaakt , die ook de bankinterface implementeert en een verwijzing naar een andere bankinterface opslaat. De methoden van deze klasse controleren of de gebruiker de accounteigenaar of een bankmanager is. Als dat niet het geval is, wordt er een SecurityException gegenereerd."

"Zo werkt het in de praktijk:"

Code zonder veiligheidscontroles:
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank.setUserMoney(user, 1000000);
Code met veiligheidscontroles:
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank = new BankSecurityProxy(bank);
bank.setUserMoney(user, 1000000);

"In het eerste voorbeeld maken we een bankobject en roepen we de methode setUserMoney aan .

"In het tweede voorbeeld verpakken we het originele bankobject in een BankSecurityProxy- object. Ze hebben dezelfde interface, dus de daaropvolgende code blijft werken zoals het deed. Maar nu wordt er elke keer dat een methode wordt aangeroepen een beveiligingscontrole uitgevoerd."

"Koel!"

"Ja. Je kunt veel van zulke proxy's hebben. Je zou bijvoorbeeld een andere proxy kunnen toevoegen die controleert of het rekeningsaldo te hoog is. De bankdirecteur kan besluiten om veel geld op zijn eigen rekening te zetten en met het geld naar Cuba te vluchten. ."

"Bovendien... De creatie van al deze ketens van objecten kan in een BankFactory- klasse worden geplaatst, waar je degene die je nodig hebt kunt in- of uitschakelen."

" BufferedReader werkt met een soortgelijk principe. Het is een Reader , maar het doet extra werk."

"Met deze aanpak kun je een object met de benodigde functionaliteit "samenstellen" uit verschillende "stukken".

"Oh, ik was het bijna vergeten. Proxy's worden veel breder gebruikt dan wat ik je net heb laten zien. Over andere toepassingen kun je hier lezen ."

Brug patroon

Patronen: adapter, proxy, brug - 2

"Soms is het tijdens de uitvoering van een programma nodig om de functionaliteit van een object aanzienlijk te wijzigen. Stel bijvoorbeeld dat je een spel hebt met een ezelkarakter dat later door een magiër in een draak wordt veranderd. De draak heeft totaal ander gedrag en eigenschappen, maar het is hetzelfde voorwerp!"

"Kunnen we niet gewoon een nieuw object maken en daarmee klaar zijn?"

"Niet altijd. Stel dat je ezel bevriend is met een stel personages, of misschien was hij onder invloed van verschillende spreuken, of was hij betrokken bij bepaalde zoektochten. Met andere woorden, het object kan al op veel plaatsen in gebruik zijn - en gekoppeld aan tal van andere objecten. In dit geval is het dus geen optie om zomaar een nieuw object aan te maken."

"Nou, wat kan er dan gedaan worden?"

"Het Bridge-patroon is een van de meest succesvolle oplossingen."

"Dit patroon houdt in dat een object in twee objecten wordt gesplitst: een «interface-object» en een «implementatie-object».'

"Wat is het verschil tussen de interface en de klasse die deze implementeert?"

"Met een interface en een klasse eindigen we met één object. Maar hier hebben we er twee. Kijk naar dit voorbeeld:"

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

"En dan kun je verschillende subklassen van UserImpl declareren, bijvoorbeeld UserDonkey (ezel) en UserDragon (draak)."

"Toch begrijp ik niet zo goed hoe dit gaat werken."

"Nou, zoiets:"

Voorbeeld
class User
{
 private UserImpl realUser;

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

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

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

"Dus het is zoiets als een proxy."

"Ja, maar in een proxy kan het hoofdobject ergens apart worden opgeslagen, en de code werkt in plaats daarvan met proxy's. Hier zeggen we dat iedereen met het hoofdobject werkt, maar dat de onderdelen intern veranderen."

"Ah. Bedankt. Kun je me een link geven om er meer over te lezen?"

"Natuurlijk, Amigo, mijn vriend. Alsjeblieft: brugpatroon ."