design-patterns-proxy.html 9.33 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
<p>Ein Proxy hat den Zweck, dass ein Client nicht direkt mit einem Zielobjekt kommunizieren kann. Stattdessen läuft die Kommunikation zwischen Client und Zielobjekt stets über den zwischengeschalteten Stellvertreter/Proxy. Dabei wird die ursprüngliche Anfrage des Client ggf. manipuliert, aufgezeichnet, verzögert, gänzlich blockiert oder ähnliches.</p>

<img src="media/patterns_proxy_illustration.png" style="width:600px">
<label>Illustration eines Stellvertreters (Proxy), der die direkte Kommunikation verhindert</label>

<p>Der Proxy ist eine Klasse, die dieselbe Schnittstelle wie das eigentliche Zielobjekt erfüllt und daher an dessen Stelle eingesetzt werden kann. Im folgenden UML-Klassendiagramm wird das Proxy-Muster dargestellt. Der Proxy delegiert die Anfrage des Client an das Zielobjekt <code>RealObject</code>, welches ihm zu diesem Zweck bekannt ist (Attribut <code>Proxy.realObject</code>, s. Implementierung der Methode <code>Proxy.operation</code>). Im Gegensatz zum Adapter verändert der Proxy die Schnittstelle nicht, sondern implementiert sie seinerseits vollständig, wobei eine zusätzliche Funktionalität ergänzt wird, um das Zielobjekt zu entlasten.</p>

<img src="media/patterns_proxy.png" style="width:450px">
<label>Entwurfsmuster Proxy</label>

11
<p>Das Proxy-Muster bringt zunächst einen eindeutigen Nachteil mit sich: Es entsteht durch die Proxy-Klasse zusätzlicher Code, der die Komplexität im Call-Stack und den verbundenen Aufwand für Test und Wartung erhöht. Für die zusätzliche Kontrollebene, die durch den Proxy gewonnen wird, muss es also gute Gründe geben, die folgend exemplarisch vorgestellt werden. Voraussetzung für die Notwendigkeit einer Proxy-Klasse ist es, dass die dahinterliegende Klasse nicht verändert werden kann oder soll.</p>
12
13

<ul>
14
<li><b>Schutz-Proxy</b>: Ein Schutz-Proxy fängt den Zugriff auf das echte Zielobjekt ab, um diesen ggf. nach Prüfung der Autorisierung einzuschränken. Ein solcher Proxy kann eingesetzt werden, wenn die abzusichernde Klasse selbst nicht angepasst werden kann, weil ihr Code nicht zugänglich ist oder Änderungen nicht zulässig sind (z.B. aus lizenzrechtlichen Gründen).</li>
15
16
17
18
19
20
<li><b>Remote-Proxy</b>: Ein Remote-Proxy ist ein Stellvertreterobjekt auf der Seite des Client, wenn dieser Methoden auf Remote-Objekten aufruft, die in einer anderen Laufzeitumgebung (z.B. einer entfernten JVM) residieren. Die andere Laufzeitumgebung wird in einem anderen Prozess und Adressraum ausgeführt, meistens sogar auf einem anderen Host, und bietet einen <i>Remote Service</i> an, der von außen aufgerufen werden kann (s. Kapitel <a href="#unit-rmi" class="navigate">Remote Method Invocation</a> und <a href="#unit-soap" class="navigate">SOAP-Webservices</a>). Der Remote-Proxy ist der Endpunkt für diese Interprozesskommunikation im Adressraum des Client – als Stellvertreter des Remote-Objekts im entfernten Adressraum. Der Proxy übernimmt das Verpacken der Input-Argumente bei einem Methodenaufruf auf dem Remote-Objekt in ein Serialisierungsformat (z.B. <code>java.io.Serializable</code>) und das entsprechende Entpacken des Return-Arguments, wenn die Antwort eingeht. Der Proxy delegiert die erforderliche Netzwerkkommunikation an die Laufzeitumgebung bzw. das Betriebssystem und kümmert sich um Fehlerfälle wie z.B. <i>Timeouts</i>.</li>
<li><b>Virtueller Proxy</b>: Ein virtueller Proxy wird eingesetzt, wenn die Erzeugung des Zielobjekts sehr aufwändig ist, d.h. es werden dazu viele Ressourcen beansprucht. Der Proxy kann in diesem Fall die vollständige Erzeugung des Zielobjekts verzögern oder sie in einzelne Teilschritte zerlegen. Da der Proxy den Zugriff auf das Zielobjekt kontrolliert, kann er dessen einzelne Teile ggf. erst bei Bedarf, d.h. beim ersten Zugriff, erzeugen lassen (= <i>Lazy Loading</i>). Ein virtueller Proxy eignet sich wenn das eigentliche Zielobjekt groß und behäbig ist, z.B. beim Einlesen eines komplexen Objekts aus einer sehr großen Datei oder Datenbank. Ein virtueller Proxy lässt sich häufig sinnvoll mit einem Remote-Proxy kombinieren, wenn über eine Netzwerkverbindung mit eingeschränkter Datenübertragungsrate kommuniziert wird, z.B. beim Mobilfunk mit schlechtem Empfang.</li>
</ul>

<p>Im folgenden Code-Beispiel zur Verdeutlichung des Proxy-Musters besteht die Funktion des Zielobjekts <code>RealVideo</code> darin eine Video-Datei aus dem Web zu laden und abzuspielen. Das Laden des Videos findet im Konstruktor der Klasse <code>RealVideo</code> statt (Zeile 6). Das Abspielen startet bei Aufruf der Methode <code>play</code> (Zeilen 10-14). Das Interface <code>Video</code> schreibt nur diese Methode <code>play</code> vor, die auch von der Proxy-Klasse <code>ProxyVideo</code> implementiert wird. Die Proxy-Klasse verändert das Verhalten des Zielobjekts um zwei Aspekte:
<ul>
21
<li>Das Laden des Videos wird verzögert und findet nicht im Konstruktor, sondern erst direkt vor dem Abspielen in der Methode <code>play</code> statt (Lazy Loading).</li>
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
<li>Videos, die kürzer sind als 30 Sek., werden gar nicht erst abgespielt, da es sich (in diesem Fall) um Werbung handelt.</li>
</ul></p>

<ul class="nav nav-tabs" id="proxy-tabs" role="tablist">
  <li class="nav-item"><a href="#proxy-tabs-realvideo" class="nav-link active" data-toggle="tab" role="tab">RealVideo</a></li>
  <li class="nav-item"><a href="#proxy-tabs-video" class="nav-link" data-toggle="tab" role="tab">Video</a></li>
  <li class="nav-item"><a href="#proxy-tabs-proxyvideo" class="nav-link" data-toggle="tab" role="tab">ProxyVideo</a></li>
  <li class="nav-item"><a href="#proxy-tabs-client" class="nav-link" data-toggle="tab" role="tab">Client</a></li>
</ul>
<div class="tab-content" id="proxy-tabs-content">
  <div class="tab-pane show active" id="proxy-tabs-realvideo" role="tabpanel">
	<pre><code class="language-java line-numbers">class RealVideo implements Video {

    MediaPlayer player;

    RealVideo(String src) {
        player = new MediaPlayer(new Media(src));
    }

    @Override
    public void play(MediaView mediaView) {
        mediaView.setMediaPlayer(player);
        System.out.println("Start playing " + player.getMedia().getSource());
        player.play();
    }
}</code></pre> 
  </div>
  <div class="tab-pane" id="proxy-tabs-video" role="tabpanel">
	<pre><code class="language-java line-numbers">interface Video {
    void play(MediaView mediaView);
}</code></pre> 
  </div>
  <div class="tab-pane" id="proxy-tabs-proxyvideo" role="tabpanel">
	<pre><code class="language-java line-numbers">class ProxyVideo implements Video {

    RealVideo video;
    String src;

    ProxyVideo(String src) {
        // video is not loaded here
        this.src = src;
    }

    @Override
    public void play(MediaView mediaView) {
        // lazy loading >> video is loaded only when played and not in constructor
        if (video == null) {
            video = new RealVideo(src);
            video.player.setOnReady(() -> checkBeforePlay(mediaView));
        }
        else { checkBeforePlay(mediaView); }
    }

    void checkBeforePlay(MediaView mediaView) {
        // don't play short advertising videos (duration &lt; 30 s)
        Double duration = video.player.getMedia().getDuration().toSeconds();
        if (duration &lt; 30) {
            System.out.printf("Ignored an advertising video %s (%.2f s)\n", src, duration);
            return;
        }
        video.play(mediaView);
    }
}</code></pre> 
  </div>
  <div class="tab-pane" id="proxy-tabs-client" role="tabpanel">
	<pre><code class="language-java line-numbers">public class Client extends Application { // the client is a JavaFX application

    final static String VIDEO_URL = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/";

    public static void main(String[] args) { launch(args); } // JavaFX application launcher

    @Override
    public void start(Stage stage) {
        MediaView mediaView = new MediaView();

        List&lt;String> filenames = List.of("BigBuckBunny.mp4", "ElephantsDream.mp4", "ForBiggerBlazes.mp4", "ForBiggerEscapes.mp4");
98
99
        List&lt;Video> videos = filenames.stream().map(filename -> new ProxyVideo(VIDEO_URL + filename))
            .collect(Collectors.toList());
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114

        Button b = new Button("Play next video");
        b.setOnAction(e -> {
            Video anyVideo = videos.get(new Random().nextInt(videos.size()));
            anyVideo.play(mediaView);  // play random video
        });

        Scene scene = new Scene(new VBox(mediaView, b));
        stage.setScene(scene);
        stage.show();
    }
}</code></pre> 
  </div>  
</div>

115
<p>Der Client startet eine einfache JavaFX-Anwendung, die jedes Mal, wenn der Anwender auf einen Button klickt, ein zufälliges Video abspielt (Zeilen 15-19 in <code>Client</code>). Zuvor wird eine Liste mit <code>ProxyVideo</code>-Objekten befüllt (Zeile 12). In dieser Zeile könnte der Konstruktoraufruf <code>new ProxyVideo</code> durch <code>new RealVideo</code> ersetzt werden, um den Proxy nicht zu verwenden, und demzufolge die Videos bereits vorab zu laden und die kurzen Werbevideos abzuspielen anstatt sie zu überspringen.</p>
116
117
118
119
120
121

<p>Das folgende UML-Klassendiagramm entspricht dem Code-Beispiel für das Proxy-Muster.</p>
<img src="media/patterns_proxy_example.png" style="width:500px">
<label>Anwendungsbeispiel für das Proxy-Muster</label>

<p>Die gezeigten Code-Beispiele zum Entwurfsmuster Proxy finden sich im Verzeichnis <a href="/patterns/proxy" class="repo-link">/patterns/proxy</a> des Modul-Repository.</p>