Posted on Leave a comment

Software Entwurfsmuster

Software Entwurfsmuster

Entwurfsmuster, im englischen Design oder Software Patterns genannt, sind Lösungsschablonen für häufig auftretende Probleme in der Softwareentwicklung, insbesondere im Design einer Softwarearchitektur. Sie ermöglichen es, flexible und stabile Software zu entwickeln, indem bewährte Strategien und Ansätze generisch und wiederverwendbar definiert werden. Jedes Pattern ist dabei auf einen oder zwei Aspekte der Software spezialisiert, die unabhängig beleuchtet werden.

Wichtige Grundkonzepte

Bevor man sich im Detail mit Entwurfsmustern auseinandersetzen kann, müssen verschiedene Grundbegriffe insbesondere aus der objektorientierten Programmierung klar sein. Die wichtigsten Konzepte sind Vererbungabstrakte und konkrete KlassenInterfaces bzw. SchnittstellenPolymorphie und lose Kopplung bzw. hohe Kohäsion.

  • Vererbung: Vererbung ist eines der wichtigsten Grundkonzepte der Objektorientierung. Eine Klasse wird dabei von einer anderen Basisklasse abgeleitet und hat dadurch Zugriff auf deren Felder und Methoden. Die abgeleitete Klasse kann die Basisklasse um eigene Felder und Methoden erweitern oder bestehende Methoden mit eigener Logik überschreiben

  • Abstrakte Klasse: Eine abstrakte Klasse kann nicht instanziiert werden, das heißt man kann kein Objekt der Klasse erzeugen. Stattdessen dient sie als Rumpf für konkrete Klassen, indem sie einen bestimmten Aufbau vorgibt oder Methoden definiert, die auf einer konkreten Klasse vorhanden sein müssen. Diese Methoden können bereits über eine Basisimplementierung verfügen

  • Konkrete Klasse: Eine konkrete Klasse ist eine Klasse, die sich von einer abstrakten Klasse ableitet – sie besitzt und erweitert gegebenenfalls ihre Felder und Methoden 

  • Interface bzw. Schnittstelle: Ein Interface ist eine abstrakte Klasse, die über keinerlei Implementierung oder Logik verfügt. Das heißt, eine Schnittstelle gibt lediglich an, was eine Klasse, die das Interface implementiert, können muss und wie die Methodensignaturen aussehen. Eine Methodensignatur umfasst den Namen der Methode, ihren Rückgabewert und alle Eingabeparameter

  • Polymorphie: Polymorphie bedeutet Vielseitigkeit und meint im Kontext der Softwareentwicklung, dass eine Variable als Wert Objekte unterschiedlichen Datentyps annehmen kann. Als Beispiel sei die abstrakte Klasse AbstractClass mit einer Methode DoSomething und den konkreten Klassen ClassA und ClassB betrachtet. Wird eine Variable x deklariert, auf der die Methode DoSomething aufgerufen wird, ist dies sowohl möglich, wenn x ein Objekt von ClassA oder ClassB ist. Der Datentyp ist damit austauschbar

  • Lose Kopplung: Generell gilt es als guter Stil, wenn Objekte und Klassen lose gekoppelt sind – sie wissen also möglichst wenig über die konkreten Implementierungen anderer Klassen und können unabhängig voneinander geändert werden. Gleichzeitig sollten sie eine hohe Kohäsion bzw. einen starken Zusammenhang haben. Das bedeutet, das alles was in einer Klasse liegt, tatsächlich miteinander zu tun hat

Entstehung und Einteilung von Entwurfsmustern

Entwurfsmuster gehen auf die „Gang Of Four“ bestehend aus Erich Gamma, Richard Helm, Ralph Johnson und John Vlissides zurück. Diese waren die ersten, die Entwurfsmuster – die es in anderen Formen bereits gab – in einem Katalog zusammengefasst haben. Ihr Buch „Design Pattern, Elements of Reusable Object-Oriented Software“ erschien 1995 und ist heute immer noch lesenswert.

In ihrem Buch unterteilen die Autoren Entwurfsmuster nach zwei Kategorien: Zum einen nach ihrem Zweck (Purpose) und zum anderen nach dem Bereich, auf den sie wirken (Scope). Wird nach dem Zweck eingeteilt, ergeben sich drei verschiedene Typen: Erzeugungsmuster (Creational Patterns), Strukturmuster (Structural Patterns) und Verhaltensmuster (Behavioral Patterns). Erzeugungsmuster beschäftigen sich mit dem Klassendesign und der Erstellung bzw. Instanziierung von Objekten, während Strukturmuster das Zusammenwirken und die Beziehungen von Klassen oder Objekten zueinander definieren. Verhaltensmuster sind die größte Gruppe von Entwurfsmustern und beschreiben, was eine bestimmte Klasse oder ein Objekt tun soll. In diese Kategorie fällt auch die Kommunikation oder das Versenden von Nachrichten und Aufrufen zwischen Objekten. 

Teilt man Entwurfsmuster wiederum nach ihrem Anwendungsbereich ein, wirken sich die Muster entweder auf die Klassenstruktur oder die Objektstruktur der Software aus. Während Klassenmuster sich viel mit Vererbungshierarchien der Programmklassen beschäftigen, definieren Objektmuster die Beziehungen oder die Zusammensetzung von konkreten Objekten. Klassenmuster sind zudem zu der Zeit, zu der das Programm übersetzt wird, bereits statisch festgelegt, während Objektmuster Strukturen beschreiben, die zur Laufzeit geändert werden können.

Vor- und Nachteile von Entwurfsmustern

Entwurfsmuster bringen zwei große Vorteile mit sich: Zum einen ermöglichen sie es, einen komplexen Sachverhalt losgelöst zu betrachten, zu modellieren und darüber zu diskutieren. Das erhöht die Qualität des Designprozesses und macht die Software flexibler und besser wartbar. Zum anderen sind die Lösungen, die Entwurfsmuster bieten, erprobt. Der Entwickler, der sie anwendet, kann sich sicher sein, dass die Lösung brauchbar ist und Probleme vermeidet, die ihm vielleicht nicht einmal bewusst sind.

Trotzdem sollten Entwurfsmuster nicht blind oder überladen eingesetzt werden. Insbesondere Anfänger neigen manchmal dazu, jedes neu gelernte Muster direkt auf ihr Projekt anwenden zu wollen, obwohl das nicht immer Sinn hat. Insbesondere die Verwendung vieler Entwurfsmuster, die für den expliziten Anwendungsfall gar nicht geeignet sind, bezeichnet man als „Antipattern“.

Die wichtigsten Entwurfsmuster im Überblick

Erzeugungsmuster

Die meisten Erzeugungsmuster haben das Ziel, die Konstruktion des Objektes von seiner Repräsentation zu entkoppeln, um die Außenwelt von der Implementierung unabhängig zu machen. Zwei Ideen kehren dabei immer wieder: Zum einen das Abkapseln der Information darüber, welche Klassen ein bestimmtes System nutzt, und zum andere das Verstecken des Erstellungsprozesses selbst. Erzeugende Objektmuster delegieren einen Teil der Klassenerstellung dazu zu einem anderen Objekt, während Klassenmuster häufig mit Vererbungshierarchien oder Subklassen arbeiten. 

Die fünf wichtigsten Erzeugungsmuster sind:

  • Abstract Factory: Eine abstrakte Fabrik definiert eine Schnittstelle über die ähnliche Objekte erzeugt werden können, ohne, dass der Aufrufer die konkreten Klassen selbst benennen können muss. Angenommen, ein Programm kann mehrere Fahrzeuge wie PKWs und LKWs erstellen. Dann könnte eine abstrakte Klasse erstellt werden, die grundsätzlich eine Schnittstelle zum Erstellen von Fahrzeugen bereitstellt. Für explizite Fahrzeuge werden dann konkrete Klassen, beispielsweise eine PKWFactory oder eine LKWFactory, abgeleitet. Diese implementieren dann spezifische Logik für den jeweiligen Fahrzeugtyp

  • Factory Method: Etwas anders funktioniert das Muster Factory Method. Hier wird eine Klassenschnittstelle definiert mit der ein Objekt erstellt werden kann. Da es sich lediglich um eine Methode handelt, können weitere Klassen diese Methode überschreiben und ihren eigenen Initialisierungsprozess implementieren. So gibt es beispielsweise die Basisklasse BaseVehicle, die eine Methode Build() zur Verfügung stellt. Eine abgeleitete Klasse LKW überschreibt diese Methode mit eigener Logik, beispielsweise dem Hinzufügen weiterer Fahrzeugkomponenten. Zur Laufzeit könnten Objekte, die verschiedene Fahrzeugtypen repräsentieren, ausgetauscht werden, da sie alle dieselbe Signatur haben

  • Builder Pattern: Das Builder Pattern oder Erbauermuster ermöglicht es, ein komplexes Objekt durch mehrere Schritte zu konstruieren, um so den Erstellungsprozess zu vereinfachen und die Übersichtlichkeit zu erhöhen. So gibt es auf einer Klasse beispielsweise mehrere Methoden BuildPartA()BuildPartB(), etc., die nacheinander aufgerufen werden können. So wird ein Objekt instanziiert, das ein Subset aller möglichen Komponenten beinhaltet

  • Prototype Pattern: Beim Prototype Pattern werden Objekte einer Klasse erzeugt, indem ein typischer Prototyp als Vorlage kopiert und angepasst wird. Dies lohnt sich beispielsweise, wenn das Erzeugen einer Kopie des Objekts wesentlich weniger Zeit benötigt als das Objekt mit einer Factory immer wieder neu zu erstellen

  • Singleton Pattern: Das Singleton Pattern definiert, dass es von einer Klasse im gesamten Projekt nur ein Objekt geben darf, das global verfügbar ist

Strukturmuster

Strukturmuster fassen Klassen oder Objekte zu größeren Strukturen zusammen. Auf Klassenebene spielen dabei vor allem Schnittstellen und Interfaces eine wichtige Rolle, da diese die lose Kopplung erhalten – sie entkoppeln den Zugriff auf die Klasse von ihrer Implementierung. Strukturen auf Klassenebene können zur Laufzeit nicht geändert werden, sondern sind bereits zur Übersetzungs- bzw. Kompilierzeit festgelegt. Strukturmuster auf Objektebene ordnen die erstellten Objekte in eine Struktur ein, die zur Laufzeit änderbar ist. 

Zu den wichtigsten Strukturmustern zählen:

  • Adapter: Ein Adapter lässt Klassen zusammenarbeiten, deren Schnittstellen eigentlich nicht zusammenpassen. Dazu wird die Schnittstelle der Klasse, die benutzt werden soll, durch den Adapter so angepasst, wie der Aufrufer sie erwartet. Der Adapter stellt also die Methode AdaptedOperation() zur Verfügung, die intern dann die Operation()-Methode der ursprüngliche Klasse aufruft. Die Funktion bleibt dabei identisch, allerdings ändert sich die Signatur

  • Decorator: Ein Decorator ist eine Alternative zum Verwendung von Unterklassen. Im Grunde geht es ähnlich wie beim Adapter darum, die Funktion eines anderen Objektes zu verwenden, aber für den Aufrufer zu ändern. Ein Decorator „dekoriert“ (sprich erweitert) die Funktion der Klasse mit zusätzlicher Funktionalität. Die Signatur der Methode bleibt dabei gleich

  • Proxy: Ein Proxy (auch Vertreter oder Surrogate genannt) definiert eine Schnittstelle zu einem anderen Objekt. Über den Proxy werden dann unter anderem die Erzeugung des Objektes, aber auch Teile der Steuerung, Authentifizierung oder Zugriff auf das Objekt abgewickelt. Im Gegensatz zu Adapter und Decorator bleiben sowohl die Definition der Schnittstelle als auch ihre Funktion weitestgehend gleich

  • Facade: Eine Fassade verbirgt die Komplexität eines Systems, indem sie eine einheitliche Schnittstelle zu mehreren Schnittstellen des unterliegenden Systems anbietet. Der Konsument oder Anwender muss die unterliegende Struktur dementsprechend nicht kennen. Das unterstützt die lose Kopplung zwischen Klassen und Objekten

Wie man sieht, gibt es viele Muster, die für recht ähnliche Anwendungsfälle genutzt werden können und subtile Unterschiede besitzen. Weitere Strukturmuster, die oft verwendet werden, sind das Bridge Pattern, das Composite Pattern oder das Flyweight Pattern.

Verhaltensmuster

Verhaltensmuster beschreiben wie Objekte oder Klassen innerhalb von komplexen Kontrollflüssen miteinander interagieren. Verhaltensmuster auf Klassenebene teilen dazu den Kontrollfluss des Programms über mehrere Klassen auf oder nutzen Vererbungshierarchien, während Objektmuster beispielsweise Kompositionen oder Zusammensetzungen von Objekten nutzen, um ein bestimmtes Verhalten zu erzielen. 

Einige wichtige Verhaltensmuster sind:

  • Interpreter: Interpreter kennen die meisten – es handelt sich um die Repräsentation einer Grammatik für eine Sprache, die es ermöglicht, klare Anweisungen zu formulieren und zu verstehen. Insbesondere für wiederkehrende Probleme innerhalb einer Domäne oder eines Programms kann das sinnvoll sein, denn so lassen sich Aufrufe vereinfacht darstellen und zentralisiert ändern, indem die Bedeutung bestimmter Komponenten der Grammatik geändert wird

  • Template Method: Eine Template Method oder Schablonenmethode wird für Verhaltensweisen mit vielen Teilschritten verwendet. Der Algorithmus definiert ein Skelett für die Gesamtoperation und delegiert einzelne Teilschritte dann an Unterklassen. Dadurch ist es auch möglich, Teile des Algorithmus durch das Überschreiben der Unterklasse zu ändern, ohne den gesamten Ablauf anpassen zu müssen. An dieser Stelle ist beispielsweise die Polymorphie von Objekten relevant: Indem mehrere konkrete Klassen von derselben abstrakten Klasse abgeleitet werden, können diese zur Laufzeit beliebig ausgetauscht werden. Eine Template Method ist damit sehr flexibel und frei konfigurierbar

  • Observer: Ein sogenannter Observer oder Beobachter definiert Abhängigkeiten zwischen Objekten unter Erhalt der losen Kopplung. Dazu kann ein Objekt beliebig viele andere Objekte „abonnieren“ bzw. selbst abonniert werden. Ändert sich an einem abonnierten Objekt etwas, werden alle Abonnenten durch den Observer benachrichtigt und können darauf reagieren. Die Objekte selbst müssen dabei nicht wissen, von wem sie abonniert werden

Verhaltensmuster können bisweilen recht komplex sein, decken dafür aber einen Großteil der typischen Probleme der Softwareentwicklung ab. Insbesondere das Visitor Pattern, das Mediator Pattern und das Strategy Pattern sind Verhaltensmuster, die ebenfalls häufig angewendet werden. 

Fazit

Entwurfsmuster werden zum einen in Erzeugungs-, Struktur- und Verhaltensmuster und zum anderen nach Klassen- oder Objektbezug eingeteilt. Das Ziel aller Entwurfsmuster ist es, bewährte Lösungsansätze für flexible, robuste und gut wartbare Software mit wiederverwendbaren Schablonen zu formulieren. Dadurch können komplexe Probleme besser modelliert, diskutiert und verstanden werden. Weiterhin können neue Entwickler auf bewährte Prinzipien zurückgreifen und vermeiden dadurch häufige Fehler. Einige Entwurfsmuster können auf den ersten Blick recht kompliziert wirken – oft wird die genaue Verwendung erst klar, wenn man auf einige der Probleme während des Programmierens selbst gestoßen ist und man die Muster an einem Beispiel selbst implementiert hat. Dazu gibt es viele Tutorials und Bücher, die das Verständnis für Entwurfsmuster erhöhen und den Einstieg mit umfangreichen Codebeispielen erleichtern.

Ähnliche Produkte

Schreiben Sie einen Kommentar

Ihre E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.