Stellvertreter (Entwurfsmuster)

Der Proxy, auch Stellvertreter genannt, ist ein Entwurfsmuster aus dem Bereich der Softwareentwicklung, das zur Kategorie der Strukturmuster (englisch structural design patterns) gehört. Das Muster überträgt die Steuerung eines Objektes auf ein vorgelagertes Stellvertreterobjekt.[1] Es ist ein Entwurfsmuster der sogenannten Viererbande.

Ein Proxy in seiner allgemeinsten Form ist eine Klasse, die als Schnittstelle zu einem so genannten Subjekt auftritt. Dieses Subjekt kann beispielsweise eine Netzwerkverbindung, ein großes Objekt im Speicher, eine Datei oder eine andere Ressource sein. Als Stellvertreter dieses Subjektes kann der Proxy die Erzeugung des Subjektes sowie den Zugriff darauf steuern.

Verwendung

Der Stellvertreter hat sich in verschiedenen Anwendungsfällen als nützlich erwiesen. Je nach Verwendung unterscheidet man verschiedene Arten von Stellvertreterobjekten:

Als Remote-Proxy wird ein lokaler Stellvertreter für ein Objekt in einem anderen Adressraum bezeichnet. Er wird beispielsweise in Netzwerkanwendungen oder bei DCOM verwendet.

Ein virtueller Stellvertreter dient der Verzögerung aufwändiger Operationen auf den Zeitpunkt des tatsächlichen Bedarfs. Typische solcher teuren Operationen sind die Erzeugung oder die Veränderung eines komplexen Objektes.

Zur Durchsetzung von Zugriffsrechten auf ein Objekt kommt ein Schutzproxy zum Einsatz. Dies ist insbesondere dann nützlich, wenn unterschiedliche zugreifende Objekte verschiedene Zugriffsrechte auf das zu schützende Objekt haben sollen. Ein konkretes Beispiel für Schutzproxys sind Kernel-Proxys, welche den Zugriff auf Betriebssystemobjekte steuern.

Stellvertreter kommen ebenfalls zum Einsatz, um an den eigentlichen Zugriff auf das Objekt weitere Operationen zu binden. Das Objekt bleibt damit von diesen Operationen unabhängig. Für diese Art von Stellvertretern hat sich der Begriff der Smart References etabliert. Das Zählen von Referenzen und Persistenzoperationen sind typische Anwendungsfälle.

UML-Diagramm

Klient

Der Klient stellt das Objekt dar, welches durch den Stellvertreter auf das reale Subjekt zugreift.

Stellvertreter

Der Stellvertreter bietet nach außen hin eine zum realen Subjekt identische Schnittstelle. Er verwaltet eine Referenz auf dieses und ist eventuell auch verantwortlich für dessen Erzeugung und Löschung. Weitere Verantwortlichkeiten ergeben sich aus der Art des Stellvertreters.

Subjekt

Das Subjekt definiert die gemeinsame Schnittstelle von Stellvertreter und realem Subjekt. Dadurch wird die Verwendung von Stellvertretern anstatt realer Subjekte möglich.

Reales Subjekt

Das reale Subjekt ist das durch den Stellvertreter repräsentierte Objekt.

Beispiele

Passwortschutz

Passwortschutz von einigen Methoden innerhalb einer Klasse, z. B. Klasse Konto (mit Methoden einzahlen und auszahlen).

Der Proxy ist eine neue Klasse (KontoMitPasswort) → Assoziation zur alten Konto-Klasse. Die Methoden in der Proxyklasse fragen den Benutzer nach einem Passwort und rufen dann die Methoden der Klasse Konto auf (bei richtigem Passwort).

Ferner Zugriff

Java RMI ist eine Möglichkeit, auf entfernte (sprich in einer anderen JVM laufende) Objekte zuzugreifen, wobei sich der Zugriff nicht von dem auf lokale Objekte unterscheidet. Dies wird durch so genannte Stubs und Skeletons erreicht, die entsprechend dem Proxy-Entwurfsmuster die Schnittstelle des jeweils entsprechenden Kommunikationspartners implementieren und den Methodenaufruf an diesen (meist über ein Netzwerk) weiterleiten.

Objektorientierte Programmierung

Im objektorientierten Umfeld erlaubt der Stellvertreter somit, die Objektinitialisierung von der Objekterschaffung zu trennen. Somit werden die Kosten für den Zugriff auf ein Objekt gesenkt und eine lokale Unabhängigkeit bzw. Transparenz geschaffen.

Weiteres Anwendungsbeispiel: Kombination mit Fliegengewicht

In Situationen, in denen mehrere Kopien eines komplexen Objektes existieren müssen, kann das Proxy-Entwurfsmuster mit dem sogenannten Fliegengewicht-Entwurfsmuster kombiniert werden, um den Speicherbedarf zu senken. Dabei wird typischerweise nur eine Instanz des komplexen Objektes erzeugt, sowie mehrere kleinere Proxy-Objekte, die auf dieses Objekt verweisen und als Schnittstelle bzw. Stellvertreter agieren. Alle Operationen auf die Proxy-Objekte werden an das ursprüngliche Objekt weitergeleitet. Existieren keine Instanzen des Proxys mehr, so kann auch das ursprüngliche Objekt aus dem Speicher entfernt werden.

C++

Diese C++11 Implementierung basiert auf dem vor C++98 Beispielcode im Buch Entwurfsmuster.

#include <iostream>
#include <cstring>

typedef double Koordinate;

class Punkt {
public:
  Punkt(Koordinate x = 0.0, Koordinate y = 0.0) {}
  friend bool operator==(const Punkt&, const Punkt&) {
    return false;
  }
  friend std::ostream& operator<<(std::ostream& o, const Punkt&) {
    return o;
  }
  friend std::istream& operator>>(std::istream& i, Punkt&) {
    return i;
  }
};
static const Punkt NullPunkt;

class Event;

class Grafik {
public:
  virtual ~Grafik() = default;
  virtual void zeichne(const Punkt& position) = 0;
  virtual void bearbeiteMaus(Event& ereignis) = 0;
  virtual const Punkt& getAusmasse() = 0;
  virtual void lade(std::istream& von) = 0;
  virtual void speichere(std::ostream& nach) = 0;
protected:
  Grafik() = default;
};

class Bild : public Grafik {
public:
  Bild(const char* datei) { // lädt Bild aus Datei
    std::cout << "Bild::Bild " << datei << '\n';
  }
  virtual ~Bild() = default;
  virtual void zeichne(const Punkt& position) {
    std::cout << "Bild::zeichne " << &position << "\n";
  }
  virtual void bearbeiteMaus(Event& ereignis) {}
  virtual const Punkt& getAusmasse() {
    return NullPunkt;
  }
  virtual void lade(std::istream& von) {}
  virtual void speichere(std::ostream& nach) {}
private:
  // ...
};

class BildProxy : public Grafik {
public:
  BildProxy(const char* dateiName_)
    :bild(nullptr), ausmasse(NullPunkt), dateiName(nullptr) {
    dateiName = strdup(dateiName_);
  }
  virtual ~BildProxy() {}
  virtual void zeichne(const Punkt& position) {
    std::cout << "BildProxy::zeichne " << &position << "\n";
    getBild()->zeichne(position);
  }
  virtual void bearbeiteMaus(Event& ereignis) {
    getBild()->bearbeiteMaus(ereignis);
  }
  virtual const Punkt& getAusmasse() {
    if (NullPunkt == ausmasse) {
      ausmasse = getBild()->getAusmasse();
    }
    return ausmasse;
  }
  virtual void lade(std::istream& von) {
    von >> ausmasse >> dateiName;
  }
  virtual void speichere(std::ostream& nach) {
    nach << ausmasse << dateiName;
  }
  BildProxy(const BildProxy&) = delete; // Dreierregel
  const BildProxy& operator=(const BildProxy&) = delete;
protected:
  Bild* getBild() {
    if (nullptr == bild) {
      bild = new Bild(dateiName);
    }
    return bild;
  }
private:
  Bild* bild;
  Punkt ausmasse;
  char* dateiName;
};

class TextDokument {
public:
  TextDokument() = default;
  void fuegeHinzu(Grafik*) {}
  // ...
};

int main() {
  TextDokument* text = new TextDokument;
  // ...
  BildProxy* einBildProxy = new BildProxy("DateiName");
  text->fuegeHinzu(einBildProxy);
  
  Punkt p1 = NullPunkt;
  einBildProxy->zeichne(p1);
  Punkt p2 = NullPunkt;
  einBildProxy->zeichne(p2);
}

Die Programmausgabe ist ähnlich zu:

BildProxy::zeichne 0x7fff436da15f
Bild::Bild DateiName
Bild::zeichne 0x7fff436da15f
BildProxy::zeichne 0x7fff436da15e
Bild::zeichne 0x7fff436da15e

Programmierbeispiel für Bedarfsauswertung in PHP

// Subjekt-Interface: Der Klient hängt nur von dieser Abstraktion ab.

interface Bild {
    public function getBreite();
    public function getHoehe();
    public function getPfad();

    public function Inhalt();
}

// gemeinsame Elemente des echten Subjekts und des Stellvertreters werden hier zusammengefasst.

abstract class AbstraktesBild implements Bild {
    protected $_Breite;
    protected $_Hoehe;
    protected $_Pfad;
    protected $_Daten;

    public function getBreite() {
        return $this->_Breite;
    }

    public function getHoehe() {
        return $this->_Hoehe;
    }

    public function getPfad() {
        return $this->_Pfad;
    }
}

// echtes Subjekt

class EchtesBild extends AbstraktesBild {
    public function __construct($Pfad) {
        $this->_Pfad = $Pfad;
        list ($this->_Breite, $this->_Hoehe) = getimagesize($Pfad);
        $this->_Daten = file_get_contents($Pfad);
    }

    public function Inhalt() {
        return $this->_Daten;
    }
}

// Stellvertreter. Lädt das Bild erst bei Bedarf.

class BildStellvertreter extends AbstraktesBild {
    public function __construct($Pfad) {
        $this->_Pfad = $Pfad;
        list ($this->_Breite, $this->_Hoehe) = getimagesize($Pfad);
    }
    protected function _BedarfsLaden() {
        if ($this->_echtesBild === null) {
            $this->_echtesBild = new EchtesBild($this->_Pfad);
        }
    }
    public function Inhalt() {
        $this->_BedarfsLaden();
        return $this->_echtesBild->Inhalt();
    }
}

// Klient

class Klient {
    public function HtmlImg(Bild $img) {
        return '<img src="' . $img->getPfad() . '" alt="" width="50" height="50" />';
    }
}

function Test() {
    $Pfad = 'https://upload.wikimedia.org/wikipedia/commons/d/de/Wikipedia_Logo_1.0.png';
    $klient = new Klient();

    $image = new EchtesBild($Pfad);
    echo $klient->HtmlImg($image), "\n";

    $proxy = new BildStellvertreter($Pfad);
    echo $klient->HtmlImg($proxy), "\n";
}

Test();

Verwandte Entwurfsmuster

  • Adapter
  • Brücke
  • Decorator
Commons: Stellvertreter (Entwurfsmuster) – Sammlung von Bildern, Videos und Audiodateien

Einzelnachweise

  1. Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides: Entwurfsmuster. 5. Auflage. Addison-Wesley, 1996, ISBN 3-8273-1862-9, S. 254. 
VD
Entwurfsmuster
Erzeugungsmuster

Abstrakte Fabrik | Erbauer | Fabrikmethode | Prototyp | Singleton | Multiton | Objektpool

Strukturmuster

Adapter | Brücke | Decorator | Fassade | Fliegengewicht | Kompositum | Stellvertreter

Verhaltensmuster

Beobachter | Besucher | Interpreter | Iterator | Kommando | Memento | Schablonenmethode | Strategie | Vermittler | Zustand | Zuständigkeitskette | Interceptor | Nullobjekt | Protokollstapel

Muster für objektrelationale Abbildung

Datentransferobjekt | Table Data Gateway | Row Data Gateway | Active Record | Unit of Work | Identity Map | Lazy Loading | Identity Field | Dependent Mapping | Embedded Value | Serialized LOB | Inheritance Mapper | Metadata Mapping | Query Object | Command-Query-Responsibility-Segregation

Nachrichtenübermittlungsmuster

Message | Command Message | Document Message | Event Message | Request-Reply | Return Address | Correlation Identifier | Message Sequence | Message Expiration | Format Indicator | Message Channel | Point-to-Point Channel | Publisher-Subscriber Channel | Datatype Channel | Invalid Message Channel | Dead Letter Channel | Guaranteed Delivery | Channel Adapter | Messaging Bridge | Message Bus | Pipes-and-Filters | Message Router | Content-based Router | Message Filter | Dynamic Router | Recipient List | Splitter | Aggregator | Resequencer | Composed Message Processor | Scatter-Gather | Routing Slip | Process Manager | Message Broker | Message Translator | Envelope Wrapper | Content Enricher | Content Filter | Claim Check | Normalizer | Canonical Data Model | Message Endpoint | Messaging Gateway | Messaging Mapper | Transactional Client | Polling Consumer | Event-driven Consumer | Competing Consumers | Message Dispatcher | Selective Consumer | Durable Subscriber | Idempotent Receiver | Service Activator | Control Bus | Detour | Wire Tap | Message History | Message Store | Smart Proxy | Test Message | Channel Purger

Andere

Application Controller | Business Delegate | Data Access Object | Dependency Injection | Extension Interface | Fluent Interface | Inversion of Control (IoC) | Lock | Model View Controller (MVC) | Model View Presenter (MVP) | Model View Update (MVU) | Model View ViewModel (MVVM) | Page Controller | Registry | Remote Facade | Repository | Service Locator | Session State | Table Module | Template View | Threadpool | Transaction Script | Transform View | Two-Step View | Value Object