zurück zum Artikel

Patterns in der Softwareentwicklung: Das Brückenmuster

Rainer Grimm

(Bild: Asvolas / Shutterstock.com)

Das Brückenmuster ist ein strukturelles Muster. Es entkoppelt die Schnittstelle von seiner Implementierung.

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 klassische Buch "Design Patterns: Elements of Reusable Object-Oriented Software" (kurz Design Patterns) enthält 23 Muster, darunter das Brückenmuster, die zu den Strukturmustern gehört. Es entkoppelt die Schnittstelle von seiner Implementierung. In C++ wird oft eine vereinfachte Version verwendet: das Pimpl Idiom.

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++.

Bevor ich über das Pimpl Idiom schreibe, sind hier die Fakten zu dem Brückenmuster.

Zweck

Auch bekannt als

Anwendbarkeit

Abstraktion

RedefinedAbstraction

Implementor

ConcreteImplementor

Das Brückenmuster hat zwei Hierarchien: Eine für die Abstraktion (Schnittstelle) und eine für die Implementierung. Der Client programmiert gegen die Abstraktion, und die Abstraktion nutzt die Implementierung. Folglich können verschiedene Implementierungen der Abstraktionsschnittstelle und verschiedene Implementierungen der Implementierungsschnittstelle transparent verwendet werden. Das Brückenmuster bietet große Flexibilität, da die Abstraktion und die Implementierung variiert und während der Laufzeit des Programms ausgetauscht werden können.

Das Brückenmuster ist ein gutes Beispiel für die Kombination von Vererbung und Komposition. Einerseits hat es zwei Typenhierarchien (Vererbung) und andererseits besitzt die Abstraktion eine Implementierung (Komposition).

Beispiel

Das Beispiel zeigt eine einfache Implementierung des Brückenmusters.

// bridge.cpp

#include <iostream>

class Implementor {                           // (1)
public:
    virtual void implementation() const = 0;

    virtual ~Implementor() = default;
};
 
class ImplementorA: public Implementor {
public:
    ImplementorA() = default;
 
    void implementation() const {
        std::cout << "ImplementatorA::implementation" << '\n';
    }
};
 
class ImplementorB: public Implementor {
public:
    ImplementorB() = default;

    void implementation() const {
        std::cout << "ImplementatorB::implementation" << '\n';
    }
};

class Abstraction {                           // (2)      
public:
    virtual void function() const = 0;
    virtual ~Abstraction() = default;
};

class RefinedAbstraction: public Abstraction {
public:
    RefinedAbstraction(Implementor& impl) : 
		implementor(impl) {
    }
 
    void function() const {
        std::cout << "RefinedAbstraction::function\n";
        implementor.implementation();
    }
private:
    Implementor& implementor;
};
 
int main() {

    std::cout << '\n';

    ImplementorA implementorA;
    ImplementorB implementorB;
 
    RefinedAbstraction refinedAbstraction1(implementorA);  // (3)
    RefinedAbstraction refinedAbstraction2(implementorB);  // (4)

    Abstraction *abstraction1 = &refinedAbstraction1;
    Abstraction *abstraction2 = &refinedAbstraction2;

    abstraction1->function();

    std::cout << '\n';

    abstraction2->function();

    std::cout << '\n';

}

Die Klasse Implementor (1) ist die Schnittstelle für die Implementierungshierarchie und die Klasse Abstraction (2) die Schnittstelle für die Abstraktion. Die Instanzen redefinedAbstraction1 und redefinedAbstraction2 erhalten ihre Implementierung in ihrem Konstruktor (3 und 4).

Der folgende Screenshot zeigt die Ausgabe des Programms.

In C++ wird oft eine vereinfachte Version des Bridge Patterns verwendet.

Der Kerngedanke des Pimpl-Idioms ist, dass die Implementierung der Klasse hinter einem Zeiger versteckt wird.

Hier ist ein Rezept für die Implementierung des Pimpl-Idioms:

Bartlomiej Filipek liefert in seinem Blogbeitrag "The Pimpl Pattern - what you should know [3]" ein schönes Beispiel für das Pimpl Idiom:

// class.h
class MyClassImpl;
class MyClass
{
public:
    explicit MyClass();
    ~MyClass(); 

    // movable:
    MyClass(MyClass && rhs) noexcept;                       // (2)
    MyClass& operator=(MyClass && rhs) noexcept;            // (3)

    // and copyable
    MyClass(const MyClass& rhs);                            // (4)
    MyClass& operator=(const MyClass& rhs);                 // (5)

    void DoSth();
    void DoConst() const;

private:
    const MyClassImpl* Pimpl() const 
      { return m_pImpl.get(); }                              // (6)
    MyClassImpl* Pimpl() { return m_pImpl.get(); }           // (7)

    std::unique_ptr<MyClassImpl> m_pImpl;                    // (1)
};

// class.cpp
class MyClassImpl
{
public:
    ~MyClassImpl() = default;

    void DoSth() { }
    void DoConst() const { }
};

MyClass::MyClass() : m_pImpl(new MyClassImpl()) 
{

}

MyClass::~MyClass() = default;
MyClass::MyClass(MyClass &&) noexcept = default;
MyClass& MyClass::operator=(MyClass &&) noexcept = default;

MyClass::MyClass(const MyClass& rhs)
    : m_pImpl(new MyClassImpl(*rhs.m_pImpl))
{}

MyClass& MyClass::operator=(const MyClass& rhs) {
    if (this != &rhs) 
        m_pImpl.reset(new MyClassImpl(*rhs.m_pImpl));

    return *this;
}

void MyClass::DoSth()
{
    Pimpl()->DoSth();
}

void MyClass::DoConst() const
{
    Pimpl()->DoConst();
}

Dies sind die wichtigsten Ideen seiner Implementierung. Ich habe ein paar Zeilenmarkierungen hinzugefügt:

Was sind die Vorteile des Pimpl Idioms? Es wäre einfacher, die Implementierung MyClassImpl in die Abstraktion MyClass einzubinden.

Beginnen möchte ich mit den Vorteilen

Vorteil

Nachteile

Das Decorator Pattern [5]ist ein häufig verwendetes Strukturmuster aus dem Buch "Design Patterns: Elements of Reusable Object-Oriented Software [6]". Seine Aufgabe ist es, ein Objekt dynamisch mit Verantwortlichkeiten zu erweitern. Ich werde den Decorator in meinem nächsten Artikel genauer vorstellen. ( [7])


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

Links in diesem Artikel:
[1] https://en.wikipedia.org/wiki/Adapter_pattern
[2] https://en.wikipedia.org/wiki/Abstract_factory_pattern
[3] https://www.cppstories.com/2018/01/pimpl/
[4] https://heise.de/-3813435
[5] https://en.wikipedia.org/wiki/Decorator_pattern
[6] https://en.wikipedia.org/wiki/Design_Patterns
[7] mailto:rainer@grimm-jaud.de