Patterns in der Softwareentwicklung: Die Template-Methode

Die Schablonenmethode definiert ein Skelett eines Algorithmus und delegiert einzelne Schritte des Algorithmus an Unterklassen.

In Pocket speichern vorlesen Druckansicht
Stiliisierte Atomstruktur

(Bild: Iaroslav Neliubov/Shutterstock.com)

Lesezeit: 4 Min.
Von
  • Rainer Grimm
Inhaltsverzeichnis

Patterns sind eine wichtige Abstraktion in der modernen Softwareentwicklung. Sie bieten eine klar definierte Terminologie, eine saubere Dokumentation und das Lernen von den Besten. Die Schablonenmethode ist wohl eines der am häufigsten verwendeten Entwurfsmuster aus dem Buch "Design Patterns: Elements of Reusable Object-Oriented Software".

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

Die Grundidee der Schablonenmethode ist einfach zu verstehen. Sie definiert das Skelett eines Algorithmus, der aus einigen typischen Schritten besteht. Implementierungsklassen können nur die Schritte anpassen, aber das Skelett nicht verändern. Die Schritte werden gerne als Hook-Methoden bezeichnet.

Zweck

  • Das Skelett eines Algorithmus definieren, das aus ein paar typischen Schritten besteht.
  • Unterklassen können die Schritte anpassen, aber nicht das Skelett.

Anwendungsfall

  • Es sollen verschiedene Varianten eines Algorithmus verwendet werden.
  • Die Varianten des Algorithmus bestehen aus ähnlichen Schritten.

Struktur

AbstractClass

  • Definiert die Struktur des Algorithmus, die aus verschiedenen Schritten besteht.
  • Die Schritte des Algorithmus können virtuell oder rein virtuell sein.

ConcreteClass

  • Überschreibt gegebenenfalls die einzelnen Schritte des Algorithmus.
// templateMethod.cpp

#include <iostream>

class Sort{
public:
    virtual void processData() final { // (4)
        readData();             
        sortData();
        writeData();
    }
    virtual ~Sort() = default;
private:
    virtual void readData(){}        // (1)  
    virtual void sortData()= 0;      // (2)
    virtual void writeData(){}       // (3)
};


class QuickSort: public Sort{
    void readData() override {
        std::cout << "readData" << '\n';
    }
    void sortData() override {
        std::cout <<  "sortData" << '\n';
    }
    void writeData() override {
        std::cout << "writeData" << '\n';
    }
};

class BubbleSort: public Sort{
    void sortData() override {
        std::cout <<  "sortData" << '\n';
    }
};


int main(){

    std::cout << '\n';

    QuickSort quick;
    Sort* sort = &quick;          // (5)
    sort->processData();

    std::cout << "\n\n";

    BubbleSort bubble;
    sort = &bubble;               // (6)
    sort->processData();

    std::cout << '\n';
  
}

Das Sortieren besteht aus drei Schritten: readData (1), sortData (2) und writeData (3). Die Mitgliedsfunktionen readData und writeData bieten eine Standardimplementierung an, aber die Mitgliedsfunktion sortData() ist rein virtuell. Diese drei Schritte sind das Grundgerüst des Algorithmus processData (4). Jetzt können Quicksort (5) und Bubble Sort (6) angewendet werden.

Hier ist die Ausgabe des Programms:

Ich habe die Skelettfunktion processData und ihre drei Schritte als virtuelle Funktion implementiert. Dank der drei virtuellen Mitgliedsfunktionen tritt die späte Bindung ein und die Mitgliedsfunktionen des Laufzeitobjekts werden aufgerufen. Die Skelettfunktion als virtuell und final zu deklarieren, ist in C++ hingegen ein Overkill. final bedeutet, dass eine virtuelle Funktion nicht überschrieben werden kann.

Wenn eine Mitgliedsfunktion nicht überschreibbar sein soll, mache sie nichtvirtuell.

Non-Virtual Interface (NVI) Idiom

Die idiomatische Art, die Schablonenmethode in C++ zu implementieren, ist über ein NVI-Idiom. Non-Virtual Interface bedeutet, dass das Skelett nicht virtuell ist und die Schritte virtuell sind. Da der Client die Schnittstelle verwendet, kann das Skelett nicht geändert werden. Hier ist die entsprechende Implementierung der Schnittstelle Sort:

class Sort{
 public:
    void processData() {
        readData();
        sortData();
        writeData();
    }
    virtual ~Sort() = default;
private:
    virtual void readData(){}
    virtual void sortData()= 0;
    virtual void writeData(){}
};

Herb Sutter machte NVI im Jahr 2001 in C++ populär. In seinem Artikel Virtuality reduziert er das NVI-Idiom auf vier Richtlinien:

  • Guideline #1: Prefer to make interfaces nonvirtual, using Template Method design pattern.
  • Guideline #2: Prefer to make virtual functions private.
  • Guideline #3: Only if derived classes need to invoke the base implementation of a virtual function, make the virtual function protected.
  • Guideline #4: A base class destructor should be either public and virtual, or protected and nonvirtual.
  • Die Anwendungsfälle der Schablonenmethode und des Strategiemusters sind ziemlich ähnlich. Beide Muster ermöglichen es, Variationen eines Algorithmus bereitzustellen. Die Schablonenmethode basiert auf Klassenhierarchien, das Strategiemuster auf Objekten und Komposition. Das Strategiemuster erhält seine verschiedenen Strategien als Objekte und kann daher seine Strategien zur Laufzeit austauschen. Die Schablonenmethode kehrt den Kontrollfluss um und folgt dem Hollywood-Prinzip: "Don't call us, we call you". Das Strategiemuster ist oft eine Black Box. Es ermöglicht, eine Strategie durch eine andere zu ersetzen, ohne ihre Details zu kennen.
  • Die Fabrikmethode wird oft für bestimmte Schritte der Schablonenmethode aufgerufen.

Vorteile

  • Neue Variationen eines Algorithmus sind einfach zu implementieren, da nur neue Unterklassen erstellt werden müssen
  • Gemeinsame Schritte der Algorithmen können direkt in der Schnittstellenklasse implementiert werden

Nachteile

  • Selbst kleine Variationen eines Algorithmus erfordern jetzt die Erstellung einer neuen Klasse; dies kann zur Erstellung vieler kleiner Klassen führen
  • Das Skelett ist fest und kann nicht geändert werden. Diese Einschränkung lässt sich durch eine Skelettfunktion umgehen

Nur ein Muster aus dem Buch "Design Patterns: Elements of Reusable Object-Oriented Software" fehlt auf meiner Reise noch: das Strategiemuster. Es wird häufig in der Standard Template Library verwendet. Ich werde in meinem nächsten Artikel auf das Strategiemuster eingehen. ()