zurück zum Artikel

Patterns in der Softwareentwicklung: Das Stellvertretermuster

Rainer Grimm

(Bild: Blackboard/Shutterstock.com)

Ein Stellvertreter kontrolliert den Zugriff auf ein anderes Objekt und ermöglicht es, zusätzliche Operationen auf das ursprüngliche Objekt durchzuführen.

Patterns sind eine wichtige Abstraktion in der modernen Softwareentwicklung. Sie bieten eine klar definierte Terminologie, eine saubere Dokumentation und das Lernen von den Besten. Das Stellvertreter-Muster (Proxy Pattern) ist eines der sieben Strukturmuster aus dem Buch "Design Patterns: Elements of Reusable Object-Oriented Software [1]".

Modernes C++ – Rainer Grimm

Rainer Grimm ist seit vielen Jahren als Softwarearchitekt, Team- und Schulungsleiter tätig. Er schreibt gerne Artikel zu den Programmiersprachen C++, Python und Haskell, spricht aber auch gerne und häufig auf Fachkonferenzen. Auf seinem Blog Modernes C++ beschäftigt er sich intensiv mit seiner Leidenschaft C++.

Ein Stellvertreter kontrolliert den Zugriff auf ein anderes Objekt und ermöglicht es, zusätzliche Operationen vor oder nach dem Zugriff auf das ursprüngliche Objekt durchzuführen.

Welches Idiom ist charakteristisch für C++? RAII (Resource Acquisition Is Initialization)! Und RAII ist die C++-Methode zur Umsetzung des Stellvertretermusters.

Zweck

Auch bekannt als

Anwendungsfall

Proxy

Subject

RealSubject

Die folgenden Beispiele verwenden zwei generische Stellvertreter: std::unique_ptr und std::shared_ptr.

// proxy.cpp

#include <iostream>
#include <memory>

class MyInt{
 public:
    MyInt(int i):i_(i){}
    int getValue() const {
        return i_;
    }
 private:
    int i_;
};

int main(){

    std::cout << '\n';

    MyInt* myInt = new MyInt(1998);                     // (3)
    std::cout << "myInt->getValue(): " 
      << myInt->getValue() << '\n';

    std::unique_ptr<MyInt> uniquePtr{new MyInt(1998)};  // (1)
    std::cout << "uniquePtr->getValue(): " 
      << uniquePtr->getValue() << '\n';

    std::shared_ptr<MyInt> sharedPtr{new MyInt(1998)};  // (2)
    std::cout << "sharedPtr->getValue(): " 
      << sharedPtr->getValue() << '\n';

    std::cout << '\n';

}

Beide Smart Pointer können transparent auf die Member-Funktionen getValue von MyInt zugreifen. Es stellt keinen Unterschied dar, ob man die Memberfunktion getValue über den std::unique_ptr (1), über den std::shared_ptr (2) oder direkt über das Objekt aufruft. Alle Aufrufe geben denselben Wert zurück.

Bekannte Verwendungen

Die Smart Pointer modellieren das Stellvertreter-Muster in C++. Außerdem ist das RAII-Idiom die C++-Adaption des Stellvertretermusters. RAII ist das sehr häufig verwendete Idiom in C++. Ich werde in ein paar Zeilen mehr darüber schreiben.

Verwandte Muster

Vorteile

Nachteile

RAII steht für Resource Acquisition Is Initialization. Das wahrscheinlich wichtigste Idiom in C++ besagt, dass eine Ressource im Konstruktor angefordert und im Destruktor des Objekts freigegeben werden sollte. Der Kerngedanke ist, dass der Destruktor automatisch aufgerufen wird, wenn das Objekt seinen Gültigkeitsbereich verlässt. Anders ausgedrückt: Die Lebensdauer einer Ressource ist an die Lebensdauer einer lokalen Variablen gebunden, und C++ verwaltet die Lebensdauer von lokalen Variablen automatisch.

Es gibt einen großen Unterschied zwischen dem Stellvertretermuster und dem RAII-Idiom in C++. Im klassischen Stellvertretermuster implementieren der Stellvertreter und das Objekt dieselbe Schnittstelle. Daher ruft man eine Mitgliedsfunktion des Stellvertreters auf, und dieser Aufruf wird an das Objekt delegiert. Im Gegensatz dazu ist es typisch für RAII, dass die Operation am Objekt implizit ausgeführt wird.

Das folgende Beispiel zeigt das deterministische Verhalten von RAII in C++

// raii.cpp

#include <iostream>
#include <new>
#include <string>

class ResourceGuard{
  private:
    const std::string resource;
  public:
    ResourceGuard(const std::string& res):resource(res){
      std::cout << "Acquire the " << resource << "." <<  '\n';
    }
    ~ResourceGuard(){
      std::cout << "Release the "<< resource << "." << '\n';
    }
};

int main() {                                            // (2)

  std::cout << '\n';

  ResourceGuard resGuard1{"memoryBlock1"};              // (1)

  std::cout << "\nBefore local scope" << '\n';
  {
    ResourceGuard resGuard2{"memoryBlock2"};            // (3)
  }                                                     // (4)
  std::cout << "After local scope" << '\n';
  
  std::cout << '\n';

  std::cout << "\nBefore try-catch block" << '\n';
  try{
      ResourceGuard resGuard3{"memoryBlock3"};
      throw std::bad_alloc();                          // (5)
  }   
  catch (std::bad_alloc& e){                           // (6)
      std::cout << e.what();
  }
  std::cout << "\nAfter try-catch block" << '\n';
  
  std::cout << '\n';

}     

ResourceGuard ist ein Guard, der seine Ressource verwaltet. In diesem Fall steht der String für die Ressource. ResourceGuard erstellt in seinem Konstruktor die Ressource und gibt sie in seinem Destruktor wieder frei. Er erledigt seine Aufgabe sehr zuverlässig.

Der Destruktor von resGuard1 (1) wird am Ende der main-Funktion (2) aufgerufen. Die Lebensdauer von resGuard2 (3) endet bereits in (4). Daher wird der Destruktor automatisch ausgeführt. Auch das Auslösen einer Ausnahme hat keinen Einfluss auf die Zuverlässigkeit von resGuard3 (5). Der Destruktor wird am Ende des try-Blocks (Zeile 6) aufgerufen.

Der Screenshot zeigt die Lebensdauern der Objekte.

Es ist ziemlich einfach, dank des RAII-Idioms den ResourceGuard in einen LockGuard zu transformieren.

class LockGuard{
  private:
    static inline std::mutex m;
  public:
    LockGuard() {
        m.lock();
    }
    ~LockGuard() {
        m.unlock();
    }
};

Alle Instanzen von LockGuard teilen sich denselben Mutex m. Wenn eine Instanz den Gültigkeitsbereich verlässt, geben sie automatisch ihren Mutex m frei.

Ich habe behauptet, dass das RAII-Idiom das wichtigste Idiom in C++ ist. Als Beleg führe ich ein paar prominente Beispiele auf:

Anwendungen von RAII

In meinem nächsten Artikel werde ich meine Reise durch die Muster aus dem Buch "Design Patterns: Elements of Reusable Object-Oriented Software [5] fortsetzen. Das Beobachtermuster [6] ist ein Verhaltensmuster, das in der Werkzeugkiste eines jeden professionellen Programmierers sein sollte. (rme [7])


URL dieses Artikels:
https://www.heise.de/-7336675

Links in diesem Artikel:
[1] https://en.wikipedia.org/wiki/Design_Patterns
[2] https://en.wikipedia.org/wiki/Adapter_pattern
[3] https://en.wikipedia.org/wiki/Decorator_pattern
[4] https://en.wikipedia.org/wiki/Facade_pattern
[5] https://en.wikipedia.org/wiki/Design_Patterns
[6] https://en.wikipedia.org/wiki/Observer_pattern
[7] mailto:rme@ix.de