zurück zum Artikel

Die Struktur von Patterns in der Softwareentwicklung

Rainer Grimm

(Bild: B.Forenius/shutterstock.com)

Die Buchklassiker "Design Patterns" und "Pattern-Oriented Software Architecture" folgen einer Àhnlichen Struktur, um ihr Muster zu prÀsentieren.

Patterns sind eine wichtige Abstraktion in der modernen Softwareentwicklung. Sie bieten eine klar definierte Terminologie, eine saubere Dokumentation und das Lernen von den Besten. Bekannte BĂŒcher zum Thema sind "Design Patterns: Elements of Reusable Object-Oriented Software [1]" und "Pattern-Oriented Software Architecture, Volume 1 [2]".

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

In diesem Beitrag wende ich mich den Strukturen der Muster zu und schaue mir die Schritte genau an, denen die Autoren beim PrÀsentieren ihrer Muster folgen. Die Schritte Àhneln einander deutlich.

Bevor ich nÀher auf die Struktur eines Musters eingehe, möchte ich alle Leser auf den gleichen Wissensstand bringen und mit der Definition eines Musters nach Christopher Alexander beginnen.

Pattern: "Each pattern is a three part rule, which expresses a relation between a certain context, a problem, and a solution."

Das bedeutet, dass ein Muster eine generische Lösung fĂŒr ein Designproblem beschreibt, das in einem bestimmten Kontext immer wieder auftritt.

Um die Vorteile von Mustern zu beschreiben, greift Christopher Alexander auf die drei Begriffe "nĂŒtzlich", "brauchbar" und "verwendet" zurĂŒck.

Die beiden BĂŒcher "Design Patterns: Elements of Reusable Object-Oriented Software [4]" und "Pattern-Oriented Software Architecture, Volume 1 [5]" gehören zweifellos zu den einflussreichsten, die je ĂŒber Softwareentwicklung geschrieben wurden. Beide Werke wirken allerdings auch etwas einschlĂ€fernd. Diesen Effekt fĂŒhre ich vor allem darauf zurĂŒck, dass beide BĂŒcher ihre Muster in sich monoton wiederholenden 13 Schritten prĂ€sentieren.

Um nicht in die gleiche Falle zu tappen, bemĂŒhe ich mich, die Schritte aus dem Buch "Design Patterns: Elements of Reusable Object-Oriented Software [6]" möglichst kurz und prĂ€gnant darzustellen – und wende sie direkt auf das Strategiemuster [7]an. Die Absicht jedes Schritts ist kursiv dargestellt. Die nicht kursiven Inhalte beziehen sich auf das Strategiemuster.

Ein prÀgnanter Name, der leicht zu merken ist.

Strategiemuster

Eine Antwort auf die Frage: Wie lautet der Zweck des Musters?

Definiere eine Familie von Algorithmen, kapsle sie in Objekten und mache sie wÀhrend der Laufzeit deines Programms austauschbar.

Alternative Namen fĂŒr das Muster, falls bekannt.

Policy

Ein motivierendes Beispiel fĂŒr das Muster.

Ein Container mit Strings kann auf verschiedene Arten sortiert werden. Sie lassen sich lexikografisch, ohne BerĂŒcksichtigung der Groß- und Kleinschreibung, umgekehrt, und so weiter sortieren. Es wĂ€re ein Alptraum, mĂŒsste man seine Sortierkriterien in seinem Sortieralgorithmus fest codieren. Deutlich flexibler ist es, das Sortierkriterium in einem Objekt zu kapseln und den Sortieralgorithmus mit dem Objekt zu konfigurieren.

Situationen, in denen sich das Muster anwenden lÀsst.

Das Strategiemuster ist anwendbar, wenn

Struktur

Eine grafische Darstellung des Musters.

Klassen und Objekte, die an diesem Muster teilnehmen.

Kollaboration mit den Teilnehmern.

Der Kontext und die konkrete Strategie implementieren den gewÀhlten Algorithmus. Der Kontext leitet die Client-Anfrage an die verwendete konkrete Strategie weiter.

Wie sehen die Vor- und Nachteile des Musters aus?

Die Vorteile des Strategiemusters sind:

Implementierungstechniken des Musters.

Codeschnipsel zur Veranschaulichung der Umsetzung des Musters. Im Buch werden Smalltalk und C++ verwendet.

Das Strategiemuster ist so fest in das Design der Standard Template Library (STL) integriert, dass wir es fast nicht mehr wahrnehmen. Außerdem kommt in der STL oft eine simple Variante des Strategiemusters zum Einsatz.

Hier sind zwei von vielen Beispielen:

std::sort kann mit einem Sortierkriterium parametrisiert werden. Das Sortierkriterium muss ein binĂ€res PrĂ€dikat sein. Lambdas sind perfekt fĂŒr solche binĂ€ren PrĂ€dikate geeignet.

// strategySorting.cpp

#include <algorithm>
#include <functional>
#include <iostream>
#include <string>
#include <vector>

void showMe(const std::vector<std::string>& myVec) {
    for (const auto& v: myVec) std::cout << v << " ";
    std::cout << "\n\n";
}


int main(){

    std::cout << '\n';

    // initializing with an initializer lists
    std::vector<std::string> myStrVec = {"Only", "for", "Testing", "Purpose", "!!!!!"};
    showMe(myStrVec);     // Only for Testing Purpose !!!!! 

    // lexicographic sorting
    std::sort(myStrVec.begin(), myStrVec.end());
    showMe(myStrVec);    // !!!!! Only Purpose Testing for 

    // case insensitive first character
    std::sort(myStrVec.begin(), myStrVec.end(), 
              [](const std::string& f, const std::string& s){ return std::tolower(f[0]) < std::tolower(s[0]); });
    showMe(myStrVec);   // !!!!! for Only Purpose Testing 

    // sorting ascending based on the length of the strings
    std::sort(myStrVec.begin(), myStrVec.end(), 
              [](const std::string& f, const std::string& s){ return f.length() < s.length(); });
    showMe(myStrVec);   // for Only !!!!! Purpose Testing 

    // reverse 
    std::sort(myStrVec.begin(), myStrVec.end(), std::greater<std::string>() );
    showMe(myStrVec);   // for Testing Purpose Only !!!!! 

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

}

Das Programm strategySorting.cpp sortiert den Vektor lexikografisch, unabhĂ€ngig von der Groß- und Kleinschreibung, aufsteigend nach der LĂ€nge der Strings und in umgekehrter Reihenfolge. FĂŒr die umgekehrte Sortierung verwende ich das vordefinierte Funktionsobjekt [8] std::greater. Die Ausgabe der Applikation zeigt das Programm im Quellcode an.

Eine Policy ist eine generische Funktion oder Klasse, deren Verhalten konfiguriert werden kann. In der Regel gibt es Standardwerte fĂŒr die Policy-Parameter. std::vector und std::unordered_map sind Beispiele fĂŒr die Umsetzung von Policies in C++. NatĂŒrlich ist eine Policy eine Strategie, die zur Kompilierzeit ĂŒber Template-Parameter konfiguriert wird.

template<class T, class Allocator = std::allocator<T>>          // (1)
class vector; 

template<class Key,
    class T,
    class Hash = std::hash<Key>,                               // (3)
    class KeyEqual = std::equal_to<Key>,                       // (4)
    class allocator = std::allocator<std::pair<const Key, T>>  // (2)
class unordered_map;

Das bedeutet, dass jeder Container einen Standard-Allokator fĂŒr seine Elemente hat, der von T (Zeile 1) oder von std::pair<const Key, T> (Zeile 2) abhĂ€ngt. Außerdem verfĂŒgt std::unorderd_map ĂŒber eine Standard-Hash-Funktion (Zeile 3) und eine Standard-Gleichheitsfunktion (Zeile 4). Die Hash-Funktion berechnet den Hash-Wert auf der Grundlage des SchlĂŒssels und die Equal-Funktion kĂŒmmert sich um Kollisionen in den Buckets.

Mindestens zwei bekannte Beispiele fĂŒr die Verwendung des Musters.

Es gibt weitaus mehr AnwendungsfÀlle von Strategien in modernem C++.

template< class ExecutionPolicy, class RandomIt >
void sort( ExecutionPolicy&& policy,
           RandomIt first, RandomIt last );

Dank der Execution Policy ist es möglich, sequenziell (std::execution::seq), parallel (std::execution::par) oder parallel und vektorisiert (std::execution::par_unseq) zu sortieren.

Patterns, die eng mit diesem Pattern verwandt sind.

Strategieobjekte sollten leichtgewichtige Objekte sein. Folglich sind Lambda-AusdrĂŒcke ideal geeignet.

Wie sich ein Muster, ein Algorithmus oder ein Framework voneinander unterscheiden, möchte ich in meinem nĂ€chsten Artikel klĂ€ren und dabei auch Begriffe wie Pattern-Sequenzen und Pattern-Sprachen einfĂŒhren. (map [10])


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

Links in diesem Artikel:
[1] https://en.wikipedia.org/wiki/Design_Patterns
[2] https://en.wikipedia.org/wiki/Pattern-Oriented_Software_Architecture
[3] https://wiki.c2.com/?RuleOfThree
[4] https://en.wikipedia.org/wiki/Design_Patterns
[5] https://en.wikipedia.org/wiki/Pattern-Oriented_Software_Architecture
[6] https://en.wikipedia.org/wiki/Design_Patterns
[7] https://de.wikipedia.org/wiki/Strategie_(Entwurfsmuster)
[8] https://en.cppreference.com/w/cpp/utility/functional
[9] https://www.heise.de/blog/Projektionen-mit-Ranges-7123335.html
[10] mailto:map@ix.de