Inhaltsverzeichnis[Anzeigen]

Bridge Pattern

Zweck

Das Bridge Pattern dient dazu, das Interface von der Implementierung zu trennen.
Dies bietet in folgenden Situationen Vorteile:
  • wenn die Implementierung zur Laufzeit ausgetauscht werden soll
  • sowohl das Interface als auch die Implementierung durch Unterklassenbildung erweiterbar bleiben soll
  • der Austausch der Implementierung kein neues Übersetzen des Codes zur Folge haben soll

Auch bekannt als

  • handle/body

Struktur


Bridge Pattern
  • Das Brücken Muster besteht im wesentlichen aus drei Komponenten:
    1. einer Interfaceabstraktion, gegen die der Client programmiert ist
    2. einer Implementationsabstraktion, die von den konkreten Implementierungsklassen umgesetzt werden müssen
    3. den Implentationsklassen
  • Durch eine spezialisierte Interface Abstraktion kann das Pattern noch zusätzlich angepaßt werden.
  • Die Interface- und Implementierungsabstraktion unterstützen in der Regel nicht das gleiche Interface, sonst würden man das Pattern eher als Proxy bezeichnen.

Konsequenzen

  • dynamisch, zur Laufzeit
    • Entkopplung von der Schnittstelle und der Implementierung
      1. der Klient kann zur Laufzeit die Schnittstelle austauschen
      2. die Schnittstelle kann zur Laufzeit ihre Implementierung austauschen
      3. die Implementierung kann von mehreren Schnittstellen genützt werden
      4. MOVED TO... maximale Flexibilität zur Laufzeit, da sowohl die Abstraktion über der Schnittstellle als auch Abstraktion über der Implementierung ausgetauscht werden können
  • statisch
    • Verbesserte Erweiterbarkeit durch die Interface- und Implemtierungsabstraktion
  • Verstecken von Implementierunsdetails vor dem Clienten, so daß zusätzliche Logik ( Referenzzähler ) in die Implementierung gesteckt werden kann ( vgl. Proxy ; dort wird das Interface um zusätzliche Funktionalität erweitert )

Implementierung

  1. nur eine Implementierungsklasse
    • im degenerierten Fall, indem nur eine Implementierung vorhanden ist, ist es nicht notwendig eine Implementierungsabstraktion zu unterstützen
  2. Erzeugen des Implementierungsobjekts
    • um die konkreten Implementationsklassen zu erzeugen, auf denen die Schnittstelle arbeitet, bietet es sich an eine abstrakte Fabrik bzw. Fabrikmethode einzuführen
  3. Gemeinsame Nutzung von Implementierungsobjekten
    • im handle/body Idiom von James Coplien enthält der Body - die Implementierung - einen Zähler, der vom Handle - dem Interface - genutzt wird
  4. Mehrfachvererbung
    • die Implementierung und das Interface könnte man auch statisch, durch Vererbung zusammenführen werden, indem man privat von der Implementierung und öffentlich von dem Interface erbt
    • durch diesen Ansatz geht einerseits die ganze Flexibilität zur Laufzeit und andererseits der Name verloren

Beispiel

  • Als Beispiel der Orginalcode zum GOF Buch von 1994.
    BridgeExample.gif
  • Die Klasse Window beschreibt die Interfaceabstraktion und hält sich einen Zeiger auf die Fensterimplementierung.
class Window {
public:

Window(View* contents);

// requests handled by window
virtual void DrawContents();

virtual void Open();
virtual void Close();

virtual void Iconify();
virtual void Deiconify();

// requests forwarded to implementation
virtual void SetOrigin(const Point& at);

virtual void SetExtent(const Point& extent);
virtual void Raise();

virtual void Lower();

virtual void DrawLine(const Point&, const Point&);

virtual void DrawRect(const Point&, const Point&);

virtual void DrawPolygon(const Point[], int n);

virtual void DrawText(const char*, const Point&);



protected:
WindowImp* GetWindowImp();
View* GetView();



private:
WindowImp* _imp;
View* _contents; // the window's contents

}
  • Als Gegenstück und reine Interfacebeschreibung der Implementierung dient die Klasse WindowImpl.
  • Zu dieser Zeit nannte man solche rein virtuellen Klassen gerne Protokollklassen.
class WindowImp {

public:
virtual void ImpTop() = 0;
virtual void ImpBottom() = 0;

virtual void ImpSetExtent(const Point&) = 0;

virtual void ImpSetOrigin(const Point&) = 0;



virtual void DeviceRect(Coord, Coord, Coord, Coord) = 0;

virtual void DeviceText(const char*, Coord, Coord) = 0;

virtual void DeviceBitmap(const char*, Coord, Coord) = 0;

// lots more functions for drawing on windows...
protected:
WindowImp();
}
  • Die verschiedene Arten von Window werden durch die Interfacespezialisierungen - ApplicationWindwow, IconWindow -definiert.
class ApplicationWindow : public Window {
public:

// ...
virtual void DrawContents();
};

void ApplicationWindow::DrawContents () {

GetView()->DrawOn(this);
}


class IconWindow : public Window {

public:
// ...
virtual void DrawContents();
private:

const char* _bitmapName;
}
  • Ruft nun der Client eine Operation auf Window auf, so fungiert Window als Stellvertreter, der seine WindowImp Referenz nutzt um die Funktionalität zu unterstützen.
void Window::DrawRect (const Point& p1, const Point& p2) {

WindowImp* imp = GetWindowImp();
imp->DeviceRect(p1.X(), p1.Y(), p2.X(), p2.Y());

  • Abhängig von der referenzierten WindowImpl wird die Funktionalität auf den konkreten Windowimplementierung angesprochen.
void XWindowImp::DeviceRect (

Coord x0, Coord y0, Coord x1, Coord y1
) {
int x = round(min(x0, x1));

int y = round(min(y0, y1));
int w = round(abs(x0 - x1));

int h = round(abs(y0 - y1));
XDrawRectangle(_dpy, _winid, _gc, x, y, w, h);

}


void PMWindowImp::DeviceRect (
Coord x0, Coord y0, Coord x1, Coord y1

) {
Coord left = min(x0, x1);
Coord right = max(x0, x1);

Coord bottom = min(y0, y1);
Coord top = max(y0, y1);



PPOINTL point[4];


point[0].x = left; point[0].y = top;

point[1].x = right; point[1].y = top;

point[2].x = right; point[2].y = bottom;

point[3].x = left; point[3].y = bottom;



if (
(GpiBeginPath(_hps, 1L) == false) ||

(GpiSetCurrentPosition(_hps, &point[3]) == false) ||

(GpiPolyLine(_hps, 4L, point) == GPI_ERROR) ||

(GpiEndPath(_hps) == false)
) {

// report error


} else {
GpiStrokePath(_hps, 1L, 0L);

}
  • Die entscheidende Frage ist nun, wie kommt die Window Abstraktion an ihre Implementierung?
  • In diesem konkreten Beispiel wird sie durch eine abstrakte Fabrik, die als Singleton implementiert ist, erzeugt.

WindowImp* Window::GetWindowImp () {
if (_imp == 0) {

_imp = WindowSystemFactory::Instance()->MakeWindowImp();
}
return _imp;

}

 

Verwandte Pattern

  • Proxy
  • Adapter
  • Fassade

Variationen

Das Bridge Pattern ist insbesondere im C++ Umfeld sehr beliebt. Hilft es doch die Abhängigkeit von Interface und Implementierung aufzulösen, so daß die Compilezeiten deutlich kürzer werden. James Coplien war ein treibende Kraft bei der Entwicklung des Bridge Pattern, auch bekannt als Handle/Body Idiom und seinen Variationen, die er sehr ausführlich in dem Buch Advanced C+++ - Programming Styles and Idioms - beschreibt.

Envelope/Letter Idiom


Envelop - Letter Idiom
  • der wesentliche Unterschied zwischen dem Handle/Body Idiom und dem Envelope/Letter Idiom ist es, daß der Handle auf eine anderen Typ verweist während der Envelope einen Untertyp referenziert
  • der Envelope als Basisklasse umschließt den Letter als Unterklasse
  • dies kann man schön am String als Handle bzw. der Number als Envelope festmachen
class Number {
private:
Number* rep;
...
};
  • die Mächtigkeit/Abstraktion dieses Idioms sieht kann man an der Anweisungsfolge
Number a = Number(1.0, 1.0); // (1)

Number b = 2.0; // (2)
Number c = a + b; // (3)
schön nachvollziehen
  • die Ausdrücke (1) und (2) führen zu folgenden Konstuktoraufrufen, die explizit einen virtuellen Konstruktor nutzen

Number::Number(double d, double e) {

rep = new Complex(d, e);
}

Number::Number(double d) {
rep = new RealNumber(d);

}
  • der Client arbeitet nur noch auf der Abstraktion Number, daß seine Datentypen die Addition (3) unterstützen
  • im Gegensatz hierzu würden klassische OO-Implementierung dies Problem wohl in folgender Form lösen
MyNumber* a = new MyComplex(1.0, 1.0); 

MyDouble* b = new MyDouble(2.0);
MyComplex* c = a + b;
  • ALERT! einerseits muß man hier auf Indirektionen ( Pointer oder Referenz ) und andererseits auf den konkreten Zahlentypen arbeiten

Counted Pointer Idiom

Diese weitere Variante des Bridge Patterns, das wieder auf James Coplien zurückgeht, wird in dem Klassiker Patternorientierte Softwarearchitektur beschrieben.
  • Um die Speicherverwaltung von dynamisch, mehrfach referenzierten Objekten in C++ zu ermöglichen, stellt der Body eine Referenzähler zur Verfügung, der vom Handle aktualisiert wird.
  • damit ist es möglich, daß
    1. mehrere Clienten dasselbe Objekt referenzieren.
    2. Referenzen auf gelöschte Objekte vermieden werden
    3. Objekte, die nicht mehr benötigt werden, automatisch gelöscht werden.

Beispiel

Anwendung
  • die Handles sollen das gleiche Objekt referenzieren
Handle h(...);
{
Handle g(h);

h->TueEtwas();
g->TueEtwas();
}
h->TueEtwas();
  • g geht out of scope, wird daher automatisch gelöscht
  • h bleibt gültig MOVED TO... der Body von g wurde nicht gelöscht
Body
  • um die Zugriffkontrolle zu kapseln, enthält dieser einerseits einen privaten Kon - und Destruktor und andererseits eine Referenzzähler
  • in Leben gerufen kann er nur durch seinen Freund Handle, der gleichzeitig den vom Body angeboten refCounter verwaltet
class Body{
public:

void TueEtwas();
private:
friend class Handle;

Body();
~Body();
int refCounter;
};
Handle
  • der Handle erzeugt den Body und setzte den Referenzzähler auf 1
class Handle{

public:
Handle( ...){
body= newBody(...);

body->refCounter=1;
}
  • im Kopierkonstruktor wird der Body zugewiesen und der Referenzzähler erhöht
  Handle( const Handle& h){
body= h.body;

body->refCounter++;
}

 

  • im Zuweisungsoperator wird der Zähler des zugewiesens Body erhöht, der Body zugewiesen und er ursprüngliche Body gegebenfalls gelöscht

Handle& operator= ( const Handle& h ){

h.body->refCounter++;
if ( --body->refCounter) <= 0 ) delete body;

body= h.body;
return *this;
}
  • die Aufgabe des Destruktors ist es über die Lebenszeit des Body zu wachen
  ~Body(){
if ( --body->refCounter) <= 0 ) delete body;

}
  • die Proxy Eigenschaft des Handles sieht man am -> Operator
  Body* operator->() { return body; }
  • um den Body nicht direkt über den Handle zu manipulieren, wird dieser private erklärt
private:
Body* body;

Pimple Idiom

Beim Pointer to implementations Idiom, das auf Herb Sutter zurückgeht, wird insbesonder die Reduzierung der Compilezeit in C++ betont.
class X {
public:
/* ... public members ... */
protected:

/* ... protected members? ... */
private:
/* ... private members? ... */
struct Ximpl;
XImpl* pimpl_; // opaque pointer to

// forward-declared class
  • Durch das Auslagern der privaten, nichtvirtuellen Funktionen und privaten Variablen in den Body Ximpl, ist es möglich, Veränderungen an Ximpl durchzuführen, ohne das der Handle X neu kompiliert werden muß.
  • Gerne wird der Body durch eine Smart Pointer ersetzt, um abzusicher, daß jeder Handle an genau einen Body gebunden ist
class X{

...
struct Ximpl;
boost::scoped_ptr< Ximpl > pimpl;

...
};
  • oder das mehrere Handle einen Body referenzieren können.
class X{

...
struct Ximpl;
boost::shared_ptr< Ximpl > pimpl;

...
};
  • Die obigen Bodys werden genau dann gelöscht, wenn der letzte Handle gelöscht wird.

Abonniere den Newsletter (+ pdf Päckchen)

Beiträge-Archiv

Sourcecode

Neuste Kommentare