Übernehmen Sie die Kontrolle mit dem Proxy-Entwurfsmuster

Ein Freund von mir - nicht weniger ein Arzt - erzählte mir einmal, dass er einen Freund davon überzeugt habe, eine College-Prüfung für ihn abzulegen. Jemand, der den Platz eines anderen einnimmt, wird als Proxy bezeichnet. Unglücklicherweise für meinen Freund hat sein Stellvertreter in der Nacht zuvor etwas zu viel getrunken und den Test nicht bestanden.

In der Software erweist sich das Proxy-Entwurfsmuster in zahlreichen Kontexten als nützlich. Mit dem Java XML Pack verwenden Sie beispielsweise Proxys, um mit JAX-RPC (Java API für XML-basierte Remoteprozeduraufrufe) auf Webdienste zuzugreifen. Beispiel 1 zeigt, wie ein Client auf einen einfachen Hello World-Webdienst zugreift:

Beispiel 1. Ein SOAP-Proxy (Simple Object Access Protocol)

öffentliche Klasse HelloClient {public static void main (String [] args) {try {HelloIF_Stub proxy = (HelloIF_Stub) (neues HelloWorldImpl (). getHelloIF ()); proxy ._setTargetEndpoint (args [0]); System.out.println ( Proxy .sayHello ("Duke!")); } catch (Ausnahme ex) {ex.printStackTrace (); }}}

Der Code von Beispiel 1 ähnelt stark dem in JAX-RPC enthaltenen Beispiel für Hello World-Webdienste. Der Client erhält einen Verweis auf den Proxy und legt den Endpunkt des Proxys (die URL des Webdienstes) mit einem Befehlszeilenargument fest. Sobald der Client einen Verweis auf den Proxy hat, ruft er die sayHello()Methode des Proxys auf . Der Proxy leitet diesen Methodenaufruf an den Webdienst weiter, der sich häufig auf einem anderen Computer als dem Client befindet.

Beispiel 1 zeigt eine Verwendung für das Proxy-Entwurfsmuster: Zugriff auf entfernte Objekte. Proxys erweisen sich auch als nützlich, um teure Ressourcen bei Bedarf zu erstellen, einen virtuellen Proxy, und um den Zugriff auf Objekte zu steuern, einen Schutz-Proxy.

Wenn Sie meinen "Decorate Your Java Code" ( JavaWorld, Dezember 2001) gelesen haben , sehen Sie möglicherweise Ähnlichkeiten zwischen den Entwurfsmustern "Decorator" und "Proxy". Beide Muster verwenden einen Proxy, der Methodenaufrufe an ein anderes Objekt weiterleitet, das als reales Subjekt bezeichnet wird. Der Unterschied besteht darin, dass mit dem Proxy-Muster die Beziehung zwischen einem Proxy und dem realen Subjekt normalerweise zur Kompilierungszeit festgelegt wird, während Dekoratoren zur Laufzeit rekursiv erstellt werden können. Aber ich bin mir selbst voraus.

In diesem Artikel stelle ich zunächst das Proxy-Muster vor, beginnend mit einem Proxy-Beispiel für Swing-Symbole. Ich schließe mit einem Blick auf die integrierte Unterstützung des JDK für das Proxy-Muster.

Hinweis: In den ersten beiden Abschnitten dieser Spalte - "Überraschen Sie Ihre Entwicklerfreunde mit Designmustern" (Oktober 2001) und "Dekorieren Sie Ihren Java-Code" - habe ich das Decorator-Muster besprochen, das eng mit dem Proxy-Muster zusammenhängt Vielleicht möchten Sie sich diese Artikel ansehen, bevor Sie fortfahren.

Das Proxy-Muster

Proxy: Steuern Sie den Zugriff auf ein Objekt mit einem Proxy (auch als Ersatz oder Platzhalter bezeichnet).

Swing-Symbole sind aus Gründen, die im Abschnitt "Anwendbarkeit von Proxys" weiter unten erläutert werden, eine hervorragende Wahl zur Veranschaulichung des Proxy-Musters. Ich beginne mit einer kurzen Einführung in Swing-Symbole, gefolgt von einer Diskussion über einen Swing-Symbol-Proxy.

Swing-Symbole

Swing-Symbole sind kleine Bilder, die in Schaltflächen, Menüs und Symbolleisten verwendet werden. Sie können Swing-Symbole auch einzeln verwenden, wie in Abbildung 1 dargestellt.

Die in Abbildung 1 gezeigte Anwendung ist in Beispiel 2 aufgeführt:

Beispiel 2. Swing-Symbole

import java.awt. *; import java.awt.event. *; import javax.swing. *; // Diese Klasse testet ein Bildsymbol. öffentliche Klasse IconTest erweitert JFrame {private statische Zeichenfolge IMAGE_NAME = "mandrill.jpg"; private static int FRAME_X = 150, FRAME_Y = 200, FRAME_WIDTH = 268, FRAME_HEIGHT = 286; privates Symbol imageIcon = null, imageIconProxy = null; statisch public void main (String args []) {IconTest app = new IconTest (); app.show (); } public IconTest () {super ("Icon Test"); imageIcon = neues ImageIcon (IMAGE_NAME); setBounds (FRAME_X, FRAME_Y, FRAME_WIDTH, FRAME_HEIGHT); setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); } public void paint (Grafik g) {super.paint (g); Insets insets = getInsets (); imageIcon.paintIcon (this, g, insets.left, insets.top); }}

Die vorhergehende Anwendung erstellt ein Bildsymbol - eine Instanz von javax.swing.ImageIcon- und überschreibt dann die paint()Methode zum Zeichnen des Symbols.

Swing Image-Icon-Proxys

Die in Abbildung 1 gezeigte Anwendung verwendet Swing-Bildsymbole nur unzureichend, da Sie Bildsymbole nur für kleine Bilder verwenden sollten. Diese Einschränkung besteht, weil das Erstellen von Bildern teuer ist und ImageIconInstanzen ihre Bilder erstellen, wenn sie erstellt werden. Wenn eine Anwendung viele große Bilder gleichzeitig erstellt, kann dies zu erheblichen Leistungseinbußen führen. Wenn die Anwendung nicht alle Bilder verwendet, ist es verschwenderisch, sie im Voraus zu erstellen.

Eine bessere Lösung lädt Bilder, sobald sie benötigt werden. Zu diesem Zweck kann ein Proxy beim ersten paintIcon()Aufruf der Proxy- Methode das echte Symbol erstellen . Abbildung 2 zeigt eine Anwendung, die ein Bildsymbol (links) und einen Bildsymbol-Proxy (rechts) enthält. Das obere Bild zeigt die Anwendung unmittelbar nach ihrem Start. Da Bildsymbole ihre Bilder beim Erstellen laden, wird das Bild eines Symbols angezeigt, sobald das Anwendungsfenster geöffnet wird. Im Gegensatz dazu lädt der Proxy sein Bild erst, wenn es zum ersten Mal gezeichnet wird. Bis das Bild geladen wird, zeichnet der Proxy einen Rand um seinen Umfang und zeigt "Bild laden ..." an. Das untere Bild in Abbildung 2 zeigt die Anwendung, nachdem der Proxy sein Bild geladen hat.

Ich habe die in Abbildung 2 in Beispiel 3 gezeigte Anwendung aufgelistet:

Beispiel 3. Swing-Symbol-Proxys

import java.awt. *; import java.awt.event. *; import javax.swing. *; // Diese Klasse testet einen virtuellen Proxy, bei dem es sich um einen // Proxy handelt, der das Laden einer teuren Ressource (eines Symbols) verzögert, bis diese // Ressource benötigt wird. öffentliche Klasse VirtualProxyTest erweitert JFrame {private static String IMAGE_NAME = "mandrill.jpg"; private static int IMAGE_WIDTH = 256, IMAGE_HEIGHT = 256, SPACING = 5, FRAME_X = 150, FRAME_Y = 200, FRAME_WIDTH = 530, FRAME_HEIGHT = 286; privates Symbol imageIcon = null, imageIconProxy = null; statisch public void main (String args []) {VirtualProxyTest app = new VirtualProxyTest (); app.show (); } public VirtualProxyTest () {super ("Virtual Proxy Test"); // Erstellen Sie ein Bildsymbol und einen Bildsymbol-Proxy. imageIcon = neues ImageIcon (IMAGE_NAME); imageIconProxy = neuImageIconProxy (IMAGE_NAME, IMAGE_WIDTH, IMAGE_HEIGHT); // Setze die Grenzen des Frames und die // Standardoperation zum Schließen des Frames. setBounds (FRAME_X, FRAME_Y, FRAME_WIDTH, FRAME_HEIGHT); setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); } public void paint (Grafik g) {super.paint (g); Insets insets = getInsets (); imageIcon.paintIcon (this, g, insets.left, insets.top); imageIconProxy.paintIcon (this, g, insets.left + IMAGE_WIDTH + SPACING, // width insets.top); // Höhe}}

Beispiel 3 ist bis auf das Hinzufügen des Bildsymbol-Proxys nahezu identisch mit Beispiel 2. Die Anwendung in Beispiel 3 erstellt das Symbol und den Proxy in ihrem Konstruktor und überschreibt die paint()Methode zum Zeichnen. Bevor Sie die Implementierung des Proxys diskutieren, sehen Sie sich Abbildung 3 an, die ein Klassendiagramm des eigentlichen Subjekts des Proxys, der javax.swing.ImageIconKlasse, darstellt.

Die javax.swing.IconSchnittstelle, die das Wesen der Swing - Symbole definiert, umfasst drei Methoden: paintIcon(), getIconWidth(), und getIconHeight(). Die ImageIconKlasse implementiert die IconSchnittstelle und fügt eigene Methoden hinzu. Bildsymbole enthalten auch eine Beschreibung und einen Verweis auf ihre Bilder.

Image-Icon-Proxys implementieren die IconSchnittstelle und behalten einen Verweis auf ein Image-Icon - das eigentliche Subjekt - bei, wie das Klassendiagramm in Abbildung 4 zeigt.

Die ImageIconProxyKlasse ist in Beispiel 4 aufgeführt.

Beispiel 4. ImageIconProxy.java

// ImageIconProxy ist ein Proxy (oder Ersatz) für ein Symbol. // Der Proxy verzögert das Laden des Bildes bis zum ersten Zeichnen des Bildes. Während das Symbol sein Bild lädt, zeichnet der // Proxy einen Rahmen und die Meldung "Bild laden ..." Klasse ImageIconProxy implementiert javax.swing.Icon {privates Symbol realIcon = null; boolean isIconCreated= falsch; private String imageName; private int Breite, Höhe; public ImageIconProxy (String imageName, int width, int height) {this.imageName = imageName; this.width = width; this.height = height; } public int getIconHeight () {return isIconCreated? Höhe: realIcon.getIconHeight (); } public int getIconWidth () {return isIconCreated realIcon == null? width: realIcon.getIconWidth (); } // Die paint () -Methode des Proxys ist überladen, um einen // Rand und eine Nachricht ("Loading image ...") zu zeichnen, während das Bild // geladen wird. Nachdem das Bild geladen wurde, wird es gezeichnet. Beachten Sie //, dass der Proxy das Image erst lädt, // wenn es tatsächlich benötigt wird. public void paintIcon (letzte Komponente c, Grafik g, int x, int y) { if (isIconCreated) { realIcon.paintIcon (c, g, x, y); } else { g.drawRect(x, y, Breite-1, Höhe-1); g.drawString ("Bild wird geladen ...", x + 20, y + 20); // Das Symbol wird erstellt (dh das Bild wird geladen) // in einem anderen Thread. synchronized (this) {SwingUtilities.invokeLater (new Runnable () {public void run () {try {// Verlangsame das Laden von Bildern. Thread.currentThread (). sleep (2000); // Der ImageIcon-Konstruktor erstellt das Bild . realicon = new ImageIcon (imagename); isIconCreated = true;} catch (InterruptedException ex) {ex.printStackTrace ();} // der Komponente Symbol neu streichen , nachdem das // Symbol erstellt wurde. c.repaint (); }} ); }}}}

ImageIconProxybehält einen Verweis auf das reale Symbol mit der realIconMitgliedsvariablen bei. Wenn der Proxy zum ersten Mal gezeichnet wird, wird das reale Symbol in einem separaten Thread erstellt, damit das Rechteck und die Zeichenfolge gezeichnet werden können (die Aufrufe von g.drawRect()und werden g.drawString()erst wirksam, wenn die paintIcon()Methode zurückgegeben wird). Nachdem das reale Symbol erstellt und daher das Bild geladen wurde, wird die Komponente, die das Symbol anzeigt, neu gezeichnet. Abbildung 5 zeigt ein Sequenzdiagramm für diese Ereignisse.

Das Sequenzdiagramm in Abbildung 5 ist typisch für alle Proxys: Proxys steuern den Zugriff auf ihr reales Subjekt. Aufgrund dieser Steuerung instanziieren Proxys häufig ihr reales Motiv , wie dies bei dem in Beispiel 4 aufgeführten Bildsymbol-Proxy der Fall ist. Diese Instanziierung ist einer der Unterschiede zwischen dem Proxy-Muster und dem Dekorationsmuster: Dekorateure erstellen selten ihre realen Motive.

Die integrierte Unterstützung des JDK für das Proxy-Entwurfsmuster

Das Proxy-Muster ist eines der wichtigsten Entwurfsmuster, da es eine Alternative zur Erweiterung der Funktionalität durch Vererbung darstellt. Diese Alternative ist die Objektzusammensetzung, bei der ein Objekt (Proxy) Methodenaufrufe an ein eingeschlossenes Objekt (reales Subjekt) weiterleitet.

Die Objektzusammensetzung ist der Vererbung vorzuziehen, da bei der Komposition einschließende Objekte ihr eingeschlossenes Objekt nur über die Schnittstelle des eingeschlossenen Objekts manipulieren können, was zu einer losen Kopplung zwischen Objekten führt. Im Gegensatz dazu sind Klassen bei der Vererbung eng an ihre Basisklasse gekoppelt, da die Interna einer Basisklasse für ihre Erweiterungen sichtbar sind . Aufgrund dieser Sichtbarkeit wird die Vererbung häufig als White-Box-Wiederverwendung bezeichnet. Andererseits sind bei der Komposition die Interna des einschließenden Objekts für das eingeschlossene Objekt nicht sichtbar (und umgekehrt); Daher wird die Zusammensetzung häufig als Black-Box-Wiederverwendung bezeichnet. Wenn alle Dinge gleich sind, ist die Black-Box-Wiederverwendung (Zusammensetzung) der White-Box-Wiederverwendung (Vererbung) vorzuziehen, da eine lose Kopplung zu formbareren und flexibleren Systemen führt.

Da das Proxy-Muster so wichtig ist, wird es von J2SE 1.3 (Java 2 Platform, Standard Edition) und darüber hinaus direkt unterstützt. Diese Unterstützung umfasst drei Klassen aus dem java.lang.reflectPaket: Proxy, Method, und InvocationHandler. Beispiel 5 zeigt ein einfaches Beispiel, das die JDK-Unterstützung für das Proxy-Muster verwendet: