"Hallo, Freund!"

„Hallo, Bilaabo!“

„Wir haben noch etwas Zeit, also erzähle ich Ihnen von drei weiteren Mustern.“

„Noch drei? Wie viele sind es insgesamt?“

„Derzeit gibt es Dutzende beliebter Muster, aber die Zahl der «erfolgreichen Lösungen» ist unbegrenzt.“

„Ich verstehe. Also muss ich mehrere Dutzend Muster lernen?“

„Solange Sie keine echte Programmiererfahrung haben, werden sie Ihnen nicht viel geben.“

„Sammeln Sie besser etwas mehr Erfahrung und kehren Sie dann in einem Jahr zu diesem Thema zurück und versuchen Sie, es tiefer zu verstehen. Mindestens ein paar Dutzend der beliebtesten Designmuster.“

„Es ist eine Sünde, die Erfahrungen anderer nicht zu nutzen und stattdessen zum 110. Mal etwas zu erfinden.“

"Ich stimme zu."

„Dann fangen wir an.“

Adapter- (oder Wrapper-)Muster

Muster: Adapter, Proxy, Bridge – 1

„Stellen Sie sich vor, Sie kommen nach China und stellen fest, dass die Steckdosen einem anderen Standard entsprechen. Die Löcher sind nicht rund, sondern flach. In diesem Fall benötigen Sie einen Adapter.“

„Etwas Ähnliches kann auch in der Programmierung passieren. Klassen arbeiten auf ähnlichen, aber unterschiedlichen Schnittstellen. Wir müssen also einen Adapter zwischen ihnen erstellen.“

„So sieht es aus:“

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

interface TotalTime
{
 int getTotalSeconds();
}

„Angenommen, wir haben zwei Schnittstellen: Time  und  TotalTime .“

„Mit der Time-Schnittstelle können Sie die aktuelle Uhrzeit mithilfe der Methoden getSeconds (),  getMinutes () und  getHours () abrufen.“

„Mit der TotalTime- Schnittstelle können Sie die Anzahl der Sekunden ermitteln, die von Mitternacht bis zum aktuellen Zeitpunkt vergangen sind.“

„Was sollen wir tun, wenn wir ein TotalTime- Objekt haben, aber ein Time- Objekt benötigen oder umgekehrt?“

„Wir können hierfür Adapterklassen schreiben. Zum Beispiel:“

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

„Und ein Adapter in die andere Richtung:“

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

„Ah. Mir gefällt es. Aber gibt es Beispiele?“

„Natürlich! InputStreamReader ist beispielsweise ein klassischer Adapter. Er wandelt einen InputStream in einen Reader um.“

„Manchmal wird dieses Muster auch Wrapper genannt, weil die neue Klasse ein anderes Objekt ‚umhüllt‘.“

„ Hier können Sie noch einige andere interessante Dinge lesen .“

Proxy-Muster

„Das Proxy-Muster ähnelt in gewisser Weise dem Wrapper-Muster. Sein Zweck besteht jedoch nicht darin, Schnittstellen zu konvertieren, sondern den Zugriff auf das in der Proxy-Klasse gespeicherte Originalobjekt zu steuern. Darüber hinaus verfügen sowohl die Originalklasse als auch der Proxy normalerweise über dieselbe Schnittstelle. Dadurch ist es einfacher, ein Objekt der ursprünglichen Klasse durch ein Proxy-Objekt zu ersetzen.

"Zum Beispiel:"

Schnittstelle der realen Klasse
interface Bank
{
 public void setUserMoney(User user, double money);
 public int getUserMoney(User user);
}
Implementierung der ursprünglichen 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);
 }
}
Implementierung der Proxy-Klasse
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);
 }
}

„Im obigen Beispiel haben wir die Bank- Schnittstelle und die CitiBank- Klasse beschrieben, eine Implementierung dieser Schnittstelle.“

„Über die Schnittstelle können Sie den Kontostand eines Benutzers abrufen oder ändern.“

Und dann haben wir BankSecurityProxy erstellt , das auch die Bank- Schnittstelle implementiert und einen Verweis auf eine andere Bank-Schnittstelle speichert. Die Methoden dieser Klasse prüfen, ob der Benutzer der Kontoinhaber oder ein Bankmanager ist. Ist dies nicht der Fall, wird eine SecurityException ausgelöst.“

„So funktioniert es in der Praxis:“

Code ohne Sicherheitsprüfungen:
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank.setUserMoney(user, 1000000);
Code mit Sicherheitsüberprüfungen:
User user = AuthManager.authorize(login, password);
Bank bank = BankFactory.createUserBank(user);
bank = new BankSecurityProxy(bank);
bank.setUserMoney(user, 1000000);

„Im ersten Beispiel erstellen wir ein Bankobjekt und rufen seine setUserMoney- Methode auf.

„Im zweiten Beispiel verpacken wir das ursprüngliche Bankobjekt in ein BankSecurityProxy- Objekt. Sie haben die gleiche Schnittstelle, sodass nachfolgender Code weiterhin wie bisher funktioniert. Jetzt wird jedoch bei jedem Aufruf einer Methode eine Sicherheitsprüfung durchgeführt.“

"Cool!"

„Ja. Sie können viele solcher Proxys haben. Sie könnten zum Beispiel einen weiteren Proxy hinzufügen, der prüft, ob der Kontostand zu groß ist. Der Bankmanager könnte beschließen, viel Geld auf sein eigenes Konto einzuzahlen und mit dem Geld nach Kuba zu fliehen.“ ."

„Außerdem ... Die Erstellung all dieser Objektketten kann in einer BankFactory- Klasse abgelegt werden, in der Sie die benötigten Objekte aktivieren/deaktivieren können.“

BufferedReader funktioniert nach einem ähnlichen Prinzip. Es ist ein Reader , erledigt aber zusätzliche Arbeit.“

„Mit diesem Ansatz kann man aus verschiedenen „Teilen“ ein Objekt mit der benötigten Funktionalität „zusammensetzen“.

„Oh, fast hätte ich es vergessen. Proxys werden viel häufiger verwendet als das, was ich Ihnen gerade gezeigt habe. Über andere Verwendungszwecke können Sie hier lesen .“

Brückenmuster

Muster: Adapter, Proxy, Bridge – 2

„Manchmal ist es während der Ausführung eines Programms notwendig, die Funktionalität eines Objekts erheblich zu ändern. Nehmen wir zum Beispiel an, Sie haben ein Spiel mit einem Eselcharakter, der später von einem Magier in einen Drachen verwandelt wird. Der Drache hat völlig andere Verhaltensweisen und Eigenschaften, aber er ist es.“ das gleiche Objekt!"

„Können wir nicht einfach ein neues Objekt erstellen und damit fertig sein?“

„Nicht immer. Angenommen, Ihr Esel ist mit einer Reihe von Charakteren befreundet, stand unter dem Einfluss mehrerer Zauber oder war an bestimmten Quests beteiligt. Mit anderen Worten, das Objekt wird möglicherweise bereits an vielen Orten verwendet. und mit vielen anderen Objekten verknüpft. In diesem Fall ist es also keine Option, einfach ein neues Objekt zu erstellen.“

„Nun, was kann man dann tun?“

„Das Bridge-Muster ist eine der erfolgreichsten Lösungen.“

„Dieses Muster beinhaltet die Aufteilung eines Objekts in zwei Objekte: ein „Schnittstellenobjekt“ und ein „Implementierungsobjekt“.

„Was ist der Unterschied zwischen der Schnittstelle und der Klasse, die sie implementiert?“

„Mit einer Schnittstelle und einer Klasse haben wir am Ende ein Objekt. Aber hier haben wir zwei. Schauen Sie sich dieses Beispiel an:“

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

„Und dann können Sie mehrere Unterklassen von UserImpl deklarieren, zum Beispiel UserDonkey (Esel) und UserDragon (Drache).“

„Trotzdem verstehe ich nicht wirklich, wie das funktionieren soll.“

„Na ja, so etwas in der Art:“

Beispiel
class User
{
 private UserImpl realUser;

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

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

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

„Es ist also so etwas wie ein Proxy.“

„Ja, aber in einem Proxy könnte das Hauptobjekt irgendwo separat gespeichert werden und der Code arbeitet stattdessen mit Proxys. Hier sagen wir, dass jeder mit dem Hauptobjekt arbeitet, aber seine Teile ändern sich intern.“

„Ah. Danke. Können Sie mir einen Link geben, um mehr darüber zu lesen?“

„Natürlich, Amigo, mein Freund. Bitte schön: Brückenmuster .“