Einführung in Entwurfsmuster, Teil 1: Verlauf und Klassifizierung von Entwurfsmustern

Es wurden zahlreiche Strategien entwickelt, um die Kosten für das Entwerfen von Software zu vereinfachen und zu senken, insbesondere im Bereich der Wartung. Das Erlernen des Identifizierens und Arbeitens mit wiederverwendbaren Softwarekomponenten (gelegentlich als integrierte Software-Schaltkreise bezeichnet ) ist eine Strategie. Die Verwendung von Entwurfsmustern ist eine andere.

Dieser Artikel startet eine dreiteilige Serie zu Entwurfsmustern. In diesem Teil werde ich den konzeptionellen Rahmen von Entwurfsmustern vorstellen und eine Demonstration der Bewertung eines Entwurfsmusters für einen bestimmten Anwendungsfall durchführen. Ich werde auch die Geschichte von Designmustern und Anti-Mustern diskutieren. Abschließend werde ich die am häufigsten verwendeten Software-Designmuster klassifizieren und zusammenfassen, die in den letzten Jahrzehnten entdeckt und dokumentiert wurden.

Was ist ein Designmuster?

Das Entwerfen wiederverwendbarer objektorientierter Software, die ein vorhandenes System modelliert, ist eine echte Herausforderung. Ein Softwareentwickler muss die Entitäten des Systems in Klassen zerlegen, deren öffentliche Schnittstellen nicht übermäßig komplex sind, Beziehungen zwischen Klassen herstellen, Vererbungshierarchien offenlegen und vieles mehr. Da die meiste Software noch lange nach dem Schreiben verwendet wird, müssen Softwareentwickler auch die aktuellen Anwendungsanforderungen berücksichtigen und gleichzeitig ihren Code und ihre Infrastruktur flexibel genug halten, um zukünftige Anforderungen zu erfüllen.

Erfahrene objektorientierte Entwickler haben entdeckt, dass Software-Design-Muster die Codierung stabiler und robuster Softwaresysteme erleichtern. Die Wiederverwendung dieser Entwurfsmuster, anstatt ständig neue Lösungen von Grund auf neu zu entwickeln, ist effizient und verringert das Fehlerrisiko. Jedes Entwurfsmuster identifiziert ein wiederkehrendes Entwurfsproblem in einem bestimmten Anwendungskontext und bietet dann eine verallgemeinerte, wiederverwendbare Lösung, die auf verschiedene Anwendungsszenarien anwendbar ist.

"Ein Entwurfsmuster beschreibt die Klassen und interagierenden Objekte, die zur Lösung eines allgemeinen Entwurfsproblems in einem bestimmten Kontext verwendet werden."

Einige Entwickler definieren ein Entwurfsmuster als eine klassencodierte Entität (z. B. eine verknüpfte Liste oder einen Bitvektor), während andere sagen, dass sich ein Entwurfsmuster in der gesamten Anwendung oder im gesamten Subsystem befindet. Meiner Ansicht nach beschreibt ein Entwurfsmuster die Klassen und interagierenden Objekte, die zur Lösung eines allgemeinen Entwurfsproblems in einem bestimmten Kontext verwendet werden. Formal wird ein Entwurfsmuster als Beschreibung angegeben, die aus vier Grundelementen besteht:

  1. Ein Name , der das Entwurfsmuster beschreibt und uns ein Vokabular zur Diskussion gibt
  2. Ein Problem , das das zu lösende Entwurfsproblem zusammen mit dem Kontext identifiziert, in dem das Problem auftritt
  3. Eine Lösung für das Problem, die (in einem Software-Entwurfsmusterkontext) die Klassen und Objekte identifizieren sollte, die zum Entwurf beitragen, zusammen mit ihren Beziehungen und anderen Faktoren
  4. Eine Erklärung der Konsequenzen der Verwendung des Entwurfsmusters

Um das geeignete Entwurfsmuster zu identifizieren, müssen Sie zunächst das Problem, das Sie lösen möchten, eindeutig identifizieren. Hier ist das Problemelement der Entwurfsmusterbeschreibung hilfreich. Die Auswahl eines Entwurfsmusters gegenüber einem anderen beinhaltet normalerweise auch Kompromisse, die sich auf die Flexibilität einer Anwendung oder eines Systems und die zukünftige Wartung auswirken können. Aus diesem Grund ist es wichtig, die Konsequenzen der Verwendung eines bestimmten Entwurfsmusters zu verstehen , bevor Sie mit der Implementierung beginnen.

Auswerten eines Entwurfsmusters

Betrachten Sie die Aufgabe, eine komplexe Benutzeroberfläche mithilfe von Schaltflächen, Textfeldern und anderen Nicht-Container-Komponenten zu entwerfen. Das zusammengesetzte Entwurfsmuster betrachtet Container als Komponenten, mit denen wir Container und ihre Komponenten (Container und Nicht-Container) in anderen Containern verschachteln können, und zwar rekursiv. Wenn wir das zusammengesetzte Muster nicht verwenden würden, müssten wir viele spezialisierte Nicht-Container-Komponenten erstellen (eine einzelne Komponente, die beispielsweise ein Kennworttextfeld und eine Anmeldeschaltfläche kombiniert), was schwieriger zu erreichen ist.

Nachdem wir dies durchdacht haben, verstehen wir das Problem, das wir zu lösen versuchen, und die Lösung, die das zusammengesetzte Muster bietet. Aber was sind die Konsequenzen dieses Musters?

Die Verwendung von Composite bedeutet, dass Ihre Klassenhierarchien Container- und Nicht-Container-Komponenten mischen. Einfachere Clients behandeln Container- und Nicht-Container-Komponenten einheitlich. Und es wird einfacher sein, neue Arten von Komponenten in die Benutzeroberfläche einzuführen. Composite kann auch zu übermäßig verallgemeinerten Designs führen, wodurch es schwieriger wird, die Arten von Komponenten einzuschränken, die einem Container hinzugefügt werden können. Da Sie sich nicht auf den Compiler verlassen können, um Typeinschränkungen durchzusetzen, müssen Sie Laufzeit-Typprüfungen verwenden.

Was ist falsch an Laufzeit-Typprüfungen?

Bei der Überprüfung des Laufzeit-Typs werden if- Anweisungen und der instanceofOperator verwendet, was zu sprödem Code führt. Wenn Sie vergessen, eine Laufzeitüberprüfung zu aktualisieren, während sich Ihre Anwendungsanforderungen ändern, können Sie anschließend Fehler einführen.

Es ist auch möglich, ein geeignetes Entwurfsmuster auszuwählen und es falsch zu verwenden. Das doppelt überprüfte Verriegelungsmuster ist ein klassisches Beispiel. Durch doppelt geprüftes Sperren wird der Aufwand für die Sperrenerfassung reduziert, indem zuerst ein Sperrkriterium getestet wird, ohne die Sperre tatsächlich zu erwerben, und dann nur dann die Sperre erworben wird, wenn die Prüfung anzeigt, dass eine Sperre erforderlich ist. Während es auf dem Papier gut aussah, hatte das doppelt überprüfte Sperren in JDK 1.4 einige versteckte Komplexitäten. Als JDK 5 die Semantik des volatileSchlüsselworts erweiterte, konnten die Entwickler endlich die Vorteile des doppelt überprüften Sperrmusters nutzen.

Weitere Informationen zur doppelt überprüften Verriegelung

Siehe "Doppelte Karosserie: Clever, aber kaputt" und "Kann die doppelte Karosserie repariert werden?" (Brian Goetz, JavaWorld), um mehr darüber zu erfahren, warum dieses Muster in JDK 1.4 und früheren Versionen nicht funktioniert hat. Weitere Informationen zur Angabe von DCL in JDK 5 und höher finden Sie unter "Die Erklärung" Double-Checked Locking is Broken "(David Bacon et al.), Institut für Informatik der University of Maryland.

Anti-Muster

Wenn ein Entwurfsmuster üblicherweise verwendet wird, aber unwirksam und / oder kontraproduktiv ist, wird das Entwurfsmuster als Anti-Muster bezeichnet . Man könnte argumentieren, dass doppelt geprüftes Sperren, wie es in JDK 1.4 und früher verwendet wurde, ein Anti-Muster war. Ich würde sagen, dass es in diesem Zusammenhang nur eine schlechte Idee war. Damit sich aus einer schlechten Idee ein Anti-Pattern entwickelt, müssen die folgenden Bedingungen erfüllt sein (siehe Ressourcen).

  • Ein wiederholtes Muster von Handlungen, Prozessen oder Strukturen, das zunächst als vorteilhaft erscheint, aber letztendlich mehr schlimme Konsequenzen als vorteilhafte Ergebnisse hat.
  • Es gibt eine alternative Lösung, die klar dokumentiert, in der Praxis bewährt und wiederholbar ist.

Während das doppelt überprüfte Sperren in JDK 1.4 die erste Anforderung eines Anti-Patterns erfüllte, erfüllte es nicht die zweite: Obwohl Sie synchronizeddas Problem der verzögerten Initialisierung in einer Multithread-Umgebung lösen konnten , hätte dies den Grund dafür beseitigt Verwenden Sie zunächst die doppelt überprüfte Verriegelung.

Deadlock-Anti-Patterns

Das Erkennen von Anti-Mustern ist eine Voraussetzung, um sie zu vermeiden. Lesen Sie die dreiteilige Serie von Obi Ezechukwu, um eine Einführung in drei Anti-Muster zu erhalten, die dafür bekannt sind, Deadlocks zu verursachen:

  • Kein Schiedsverfahren
  • Arbeiteraggregation
  • Inkrementelle Verriegelung

Entwurfsmusterhistorie

Entwurfsmuster stammen aus den späten 1970er Jahren mit der Veröffentlichung von A Pattern Language: Städte, Gebäude, Bauarbeiten durch den Architekten Christopher Alexander und einige andere. In diesem Buch wurden Entwurfsmuster in einem architektonischen Kontext vorgestellt und 253 Muster vorgestellt, die zusammen das bildeten, was die Autoren als Mustersprache bezeichneten .

Die Ironie der Designmuster

Obwohl für das Software-Design verwendete Entwurfsmuster ihren Anfang auf eine Mustersprache zurückführen , wurde diese Architekturarbeit von der damals aufkommenden Sprache zur Beschreibung von Computerprogrammierung und -design beeinflusst.

Das Konzept einer Mustersprache entstand später in Donald Normans und Stephen Drapers User Centered System Design , das 1986 veröffentlicht wurde. Dieses Buch schlug die Anwendung von Mustersprachen auf das Interaktionsdesign vor , bei dem interaktive digitale Produkte, Umgebungen und Systeme entworfen werden und Dienstleistungen für den menschlichen Gebrauch.

In der Zwischenzeit hatten Kent Beck und Ward Cunningham begonnen, Muster und ihre Anwendbarkeit auf das Software-Design zu untersuchen. 1987 verwendeten sie eine Reihe von Entwurfsmustern, um die Semiconductor Test Systems Group von Tektronix zu unterstützen, die Probleme hatte, ein Entwurfsprojekt abzuschließen. Beck und Cunningham folgten Alexanders Ratschlägen für benutzerzentriertes Design (damit Vertreter der Benutzer des Projekts das Designergebnis bestimmen können) und stellten ihnen einige Designmuster zur Verfügung, um die Arbeit zu erleichtern.

Erich Gamma erkannte auch die Bedeutung wiederkehrender Entwurfsmuster bei der Arbeit an seiner Doktorarbeit. Er glaubte, dass Entwurfsmuster das Schreiben wiederverwendbarer objektorientierter Software erleichtern könnten, und überlegte, wie sie effektiv dokumentiert und kommuniziert werden könnten. Vor der Europäischen Konferenz über objektorientierte Programmierung 1991 begannen Gamma und Richard Helm, Muster zu katalogisieren.

Bei einem OOPSLA-Workshop im Jahr 1991 wurden Gamma und Helm von Ralph Johnson und John Vlissides begleitet. Diese Gang of Four (GoF), wie sie später genannt wurden, schrieb die beliebten Entwurfsmuster: Elemente wiederverwendbarer objektorientierter Software , die 23 Entwurfsmuster in drei Kategorien dokumentieren.

Die moderne Entwicklung von Designmustern

Die Entwurfsmuster haben sich seit dem ursprünglichen GoF-Buch weiterentwickelt, insbesondere da Softwareentwickler vor neuen Herausforderungen im Zusammenhang mit sich ändernden Hardware- und Anwendungsanforderungen standen.

1994 eröffnete eine in den USA ansässige gemeinnützige Organisation namens Hillside Group Pattern Languages ​​of Programs , eine Gruppe jährlicher Konferenzen, deren Ziel es ist, die Kunst der Software-Design-Muster zu entwickeln und zu verfeinern. Diese laufenden Konferenzen haben viele Beispiele für domänenspezifische Entwurfsmuster hervorgebracht. Zum Beispiel Entwurfsmuster in einem Parallelitätskontext.

Christopher Alexander bei OOPSLA

Die Grundsatzrede von OOPSLA 96 hielt der Architekt Christopher Alexander. Alexander dachte über seine Arbeit nach und darüber, wie die objektorientierte Programmiergemeinschaft bei der Übernahme und Anpassung seiner Ideen zu Mustersprachen an Software ins Schwarze getroffen und verfehlt hatte. Sie können Alexanders Ansprache vollständig lesen: "Die Ursprünge der Mustertheorie: Die Zukunft der Theorie und die Erzeugung einer lebendigen Welt."

1998 veröffentlichte Mark Grand Patterns in Java . Dieses Buch enthielt Entwurfsmuster, die im GoF-Buch nicht enthalten waren, einschließlich Parallelitätsmuster. Grand verwendete auch die Unified Modeling Language (UML), um Entwurfsmuster und ihre Lösungen zu beschreiben. Die Beispiele des Buches wurden in der Java-Sprache ausgedrückt und beschrieben.

Software-Design-Muster nach Klassifizierung

Moderne Software-Design-Muster werden basierend auf ihrer Verwendung grob in vier Kategorien eingeteilt: Kreation, Struktur, Verhalten und Parallelität. Ich werde jede Kategorie diskutieren und dann einige der wichtigsten Muster für jede auflisten und beschreiben.

Andere Arten von Entwurfsmustern

Wenn Sie denken, dass es mehr Arten von Mustern gibt, haben Sie Recht. In einem späteren Artikel dieser Reihe werden zusätzliche Entwurfsmustertypen erläutert: Interaktions-, Architektur-, Organisations- und Kommunikations- / Präsentationsmuster.

Schöpfungsmuster

Ein Erstellungsmuster abstrahiert den Prozess der Instanziierung und trennt, wie Objekte erstellt, zusammengesetzt und dargestellt werden, von dem Code, der auf ihnen basiert. Klassenerstellungsmuster verwenden die Vererbung, um die instanziierten Klassen zu variieren, und Objekterstellungsmuster delegieren die Instanziierung an andere Objekte.

  • Abstrakte Fabrik : Dieses Muster bietet eine Schnittstelle zum Einkapseln einer Gruppe einzelner Fabriken, die ein gemeinsames Thema haben, ohne ihre konkreten Klassen anzugeben.
  • Builder : Trennt die Konstruktion eines komplexen Objekts von seiner Darstellung, sodass derselbe Konstruktionsprozess verschiedene Darstellungen erstellen kann. Das Abstrahieren der Schritte der Objektkonstruktion ermöglicht unterschiedliche Implementierungen der Schritte, um unterschiedliche Darstellungen der Objekte zu konstruieren.
  • Factory-Methode : Definiert eine Schnittstelle zum Erstellen eines Objekts, lässt jedoch Unterklassen entscheiden, welche Klasse instanziiert werden soll. Mit diesem Muster kann eine Klasse die Instanziierung auf Unterklassen verschieben. Die Abhängigkeitsinjektion ist ein verwandtes Muster. (Siehe Ressourcen.)
  • Verzögerte Initialisierung : Mit diesem Muster können wir die Objekterstellung, die Datenbanksuche oder einen anderen teuren Prozess verzögern, bis das Ergebnis zum ersten Mal benötigt wird.
  • Multiton : Erweitert das Singleton-Konzept, um eine Zuordnung benannter Klasseninstanzen als Schlüssel-Wert-Paare zu verwalten, und bietet einen globalen Zugriffspunkt auf diese.
  • Objektpool : Halten Sie eine Reihe initialisierter Objekte einsatzbereit, anstatt sie bei Bedarf zuzuweisen und zu zerstören. Ziel ist es, teure Ressourcenbeschaffung und -rückgewinnung zu vermeiden, indem nicht mehr verwendete Objekte recycelt werden.
  • Prototyp : Gibt die Arten von Objekten an, die mithilfe einer prototypischen Instanz erstellt werden sollen. Erstellen Sie dann neue Objekte, indem Sie diesen Prototyp kopieren. Die prototypische Instanz wird geklont, um neue Objekte zu generieren.
  • Ressourcenerfassung ist Initialisierung : Dieses Muster stellt sicher, dass Ressourcen automatisch und ordnungsgemäß initialisiert und zurückgefordert werden, indem sie an die Lebensdauer geeigneter Objekte gebunden werden. Ressourcen werden während der Objektinitialisierung erfasst, wenn keine Chance besteht, dass sie verwendet werden, bevor sie verfügbar sind, und mit der Zerstörung derselben Objekte freigegeben, was garantiert auch bei Fehlern erfolgt.
  • Singleton : Stellt sicher, dass eine Klasse nur eine Instanz hat und einen globalen Zugriffspunkt auf diese Instanz bietet.