Erhalten Sie Zugang zu diesem und mehr als 300000 Büchern ab EUR 5,99 monatlich.
Der Bestseller von Gamma und Co. in komplett neuer Übersetzung Das Standardwerk für die objektorientierte Softwareentwicklung Zeitlose und effektive Lösungen für wiederkehrende Aufgaben im Softwaredesign Mit Design Patterns lassen sich wiederkehrende Aufgaben in der objektorientierten Softwareentwicklung effektiv lösen. Die Autoren stellen einen Katalog einfacher und prägnanter Lösungen für häufig auftretende Aufgabenstellungen vor. Mit diesen 23 Patterns können Softwareentwickler flexiblere, elegantere und vor allem auch wiederverwendbare Designs erstellen, ohne die Lösungen jedes Mal aufs Neue selbst entwickeln zu müssen. Die Autoren beschreiben zunächst, was Patterns eigentlich sind und wie sie sich beim Design objektorientierter Software einsetzen lassen. Danach werden die stets wiederkehrenden Designs systematisch benannt, erläutert, beurteilt und katalogisiert. Mit diesem Leitfaden lernen Sie, wie sich diese wichtigen Patterns in den Softwareentwicklungsprozess einfügen und wie sie zur Lösung Ihrer eigenen Designprobleme am besten eingesetzt werden. Bei jedem Pattern ist angegeben, in welchem Kontext es besonders geeignet ist und welche Konsequenzen und Kompromisse sich aus der Verwendung des Patterns im Rahmen des Gesamtdesigns ergeben. Sämtliche Patterns entstammen echten Anwendungen und beruhen auf tatsächlich existierenden Vorbildern. Außerdem ist jedes Pattern mit Codebeispielen versehen, die demonstrieren, wie es in objektorientierten Programmiersprachen wie C++ oder Smalltalk implementiert werden kann. Das Buch eignet sich nicht nur als Lehrbuch, sondern auch hervorragend als Nachschlagewerk und Referenz und erleichtert so auch besonders die Zusammenarbeit im Team. Aus dem Inhalt: Einführung Fallstudie Erzeugungsmuster Abstract Factory Builder Factory Method Prototype Singleton Strukturmuster Adapter Bridge Composite Decorator Facade Flyweight Proxy Verhaltensmuster Chain of Responsibility Command Interpreter Iterator Mediator Memento Observer State Strategy Template Method Visitor Stimmen zum Buch: »Für Designer und Entwickler objektorientierter Software ist dieses Buch von großer Bedeutung! Design Patterns stellt einen geordneten Katalog bewährter Entwurfsmus-ter zur Strukturierung, Erstellung und Manipulation von Objekten vor. Am wichtigsten ist jedoch, dass die verschiedenen Design Patterns eindeutige Bezeichnungen erhalten, die ein gemeinsames Vokabular für die Arbeit im Team bereitstellen.« - Rebecca J. Wirfs-Brock, Director, Object-Technology Services, Digitalk »Design Patterns beendet die Debatte um die Wiederverwendung von Code und zeigt das entscheidende Element der Wiederverwendbarkeit von Software auf: wiederverwendbares Design. Sie werden feststellen, dass Sie diese Patterns im Nu in Ihren eigenen Designs einsetzen und wiederverwenden.« - Steve Vinoski, Software Architect
Sie lesen das E-Book in den Legimi-Apps auf:
Seitenzahl: 580
Veröffentlichungsjahr: 2015
Das E-Book (TTS) können Sie hören im Abo „Legimi Premium” in Legimi-Apps auf:
Bereitstellung einer Schnittstelle zum Erzeugen verwandter oder voneinander abhängiger Objektfamilien ohne die Benennung ihrer konkreten Klassen.
Getrennte Handhabung der Erzeugungs- und Darstellungsmechanismen komplexer Objekte zwecks Generierung verschiedener Repräsentationen in einem einzigen Erzeugungsprozess.
Definition einer Schnittstelle zur Objekterzeugung, wobei die Bestimmung der zu instanziierenden Klasse den Unterklassen überlassen bleibt. Das Design Pattern Factory Method (Fabrikmethode) gestattet einer Klasse, die Instanziierung an Unterklassen zu delegieren.
Spezifikation der zu erzeugenden Objekttypen mittels einer prototypischen Instanz und Erzeugung neuer Objekte durch Kopieren dieses Prototyps.
Sicherstellung der Existenz nur einer einzigen Klasseninstanz sowie Bereitstellung eines globalen Zugriffspunkts für diese Instanz.
Anpassung der Schnittstelle einer Klasse an ein anderes von den Clients erwartetes Interface. Das Design Pattern Adapter (Adapter) ermöglicht die Zusammenarbeit von Klassen, die ansonsten aufgrund der Inkompatibilität ihrer Schnittstellen nicht dazu in der Lage wären.
Entkopplung einer Abstraktion von ihrer Implementierung, so dass beide unabhängig voneinander variiert werden können.
Komposition von Objekten in Baumstrukturen zur Abbildung von Teil-Ganzes-Hierarchien. Das Design Pattern Composite (Kompositum) gestattet den Clients einen einheitlichen Umgang sowohl mit individuellen Objekten als auch mit Objektkompositionen.
Dynamische Erweiterung der Funktionalität eines Objekts. Decorator-Objekte stellen hinsichtlich der Ergänzung einer Klasse um weitere Zuständigkeiten eine flexible Alternative zur Unterklassenbildung dar.
Bereitstellung einer einheitlichen Schnittstelle zu einem Schnittstellensatz in einem Subsystem. Das Design Pattern Facade (Fassade) definiert eine Schnittstelle höherer Ebene, die die Nutzung des Subsystems vereinfacht.
Gemeinsame Nutzung feingranularer Objekte, um sie auch in großer Anzahl effizient nutzen zu können.
Bereitstellung eines vorgelagerten Stellvertreterobjekts bzw. eines Platzhalters zwecks Zugriffssteuerung eines Objekts.
Unterbindung der Kopplung eines Request-Auslösers mit seinem Empfänger, indem mehr als ein Objekt in die Lage versetzt wird, den Request zu bearbeiten. Die empfangenden Objekte werden miteinander verkettet und der Request wird dann so lange entlang dieser Kette weitergeleitet, bis er von einem Objekt angenommen und abgearbeitet wird.
Kapselung eines Requests als Objekt, um so die Parametrisierung von Clients mit verschiedenen Requests, Warteschlangen- oder Logging-Operationen sowie das Rückgängigmachen von Operationen zu ermöglichen.
Definition einer Repräsentation für die Grammatik einer gegebenen Sprache und Bereitstellung eines Interpreters, der sie nutzt, um in dieser Sprache verfasste Sätze zu interpretieren.
Bereitstellung eines sequenziellen Zugriffs auf die Elemente eines aggregierten Objekts, ohne dessen zugrunde liegende Struktur offenzulegen.
Definition eines Objekts, das die Interaktionsweise eines Objektsatzes in sich kapselt. Das Design Pattern Mediator (Vermittler) begünstigt lose Kopplungen, indem es die explizite Referenzierung der Objekte untereinander unterbindet und so eine individuelle Steuerung ihrer Interaktionen ermöglicht.
Erfassung und Externalisierung des internen Zustands eines Objekts, ohne dessen Kapselung zu beeinträchtigen, so dass es später wieder in diesen Zustand zurückversetzt werden kann.
Definition einer 1-zu-n-Abhängigkeit zwischen Objekten, damit im Fall einer Zustandsänderung eines Objekts alle davon abhängigen Objekte entsprechend benachrichtigt und automatisch aktualisiert werden.
Anpassung der Verhaltensweise eines Objekts im Fall einer internen Zustandsänderung, so dass es den Anschein hat, als hätte es seine Klasse gewechselt.
Definition einer Familie von einzeln gekapselten, austauschbaren Algorithmen. Das Design Pattern Strategy (Strategie) ermöglicht eine variable und von den Clients unabhängige Nutzung des Algorithmus.
Definition der Grundstruktur eines Algorithmus in einer Operation sowie Delegation einiger Ablaufschritte an Unterklassen. Das Design Pattern Template Method (Schablonenmethode) ermöglicht den Unterklassen, bestimmte Schritte eines Algorithmus zu überschreiben, ohne dessen grundlegende Struktur zu verändern.
Darstellung einer auf die Elemente einer Objektstruktur auszuführenden Operation. Das Design Pattern Visitor (Besucher) ermöglicht die Definition einer neuen Operation, ohne die Klassen der von ihr bearbeiteten Elemente zu verändern.
Notation für Klassendiagramme
Notation für Objektdiagramme
Notation für Interaktionsdiagramme
Beziehungen der Design Patterns
Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides
Übersetzung aus dem Amerikanischen von Maren Feilen und Knut Lorenzen
Bibliografische Information der Deutschen Nationalbibliothek
Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über <http://dnb.d-nb.de> abrufbar.
ISBN 978-3-8266-9904-7
1. Auflage 2015
www.mitp.de
E-Mail: [email protected]
Telefon: +49 7953 / 7189 - 079
Telefax: +49 7953 / 7189 - 082
Authorized translation from the English language edition, entitled DESIGN PATTERNS: ELEMENTS OF REUSABLE OBJECT-ORIENTED SOFTWARE, 1st Edition, 0201633612 by GAMMA, ERICH; HELM, RICHARD, JOHNSON, RALPH; VLISSIDES, JOHN, published by Pearson Education, Inc, publishing as Addison-Wesley Professional, Copyright © 1995 by Addison-Wesley.
All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, Inc. GERMAN language edition published by mitp, an imprint of Verlagsgruppe Hüthig Jehle Rehm GmbH, Copyright © 2015.
© 2015 mitp-Verlags GmbH & Co. KG
Dieses Werk, einschließlich aller seiner Teile, ist urheberrechtlich geschützt. Jede Verwertung außerhalb der engen Grenzen des Urheberrechtsgesetzes ist ohne Zustimmung des Verlages unzulässig und strafbar. Dies gilt insbesondere für Vervielfältigungen, Übersetzungen, Mikroverfilmungen und die Einspeicherung und Verarbeitung in elektronischen Systemen.
Die Wiedergabe von Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. in diesem Werk berechtigt auch ohne besondere Kennzeichnung nicht zu der Annahme, dass solche Namen im Sinne der Warenzeichen- und Markenschutz-Gesetzgebung als frei zu betrachten wären und daher von jedermann benutzt werden dürften.
Lektorat: Sabine Schulz
Sprachkorrektorat: Maren Feilen, Knut Lorenzen
electronic publication: III-satz, Husby, www.drei-satz.de
Coverbild: © art_of_sun @ Fotolia.com
Dieses Ebook verwendet das ePub-Format und ist optimiert für die Nutzung mit dem iBooks-reader auf dem iPad von Apple. Bei der Verwendung anderer Reader kann es zu Darstellungsproblemen kommen.
Der Verlag räumt Ihnen mit dem Kauf des ebooks das Recht ein, die Inhalte im Rahmen des geltenden Urheberrechts zu nutzen. Dieses Werk, einschließlich aller seiner Teile, ist urheberrechtlich geschützt. Jede Verwertung außerhalb der engen Grenzen des Urheherrechtsgesetzes ist ohne Zustimmung des Verlages unzulässig und strafbar. Dies gilt insbesondere für Vervielfältigungen, Übersetzungen, Mikroverfilmungen und Einspeicherung und Verarbeitung in elektronischen Systemen.
Der Verlag schützt seine ebooks vor Missbrauch des Urheberrechts durch ein digitales Rechtemanagement. Bei Kauf im Webshop des Verlages werden die ebooks mit einem nicht sichtbaren digitalen Wasserzeichen individuell pro Nutzer signiert.
Bei Kauf in anderen ebook-Webshops erfolgt die Signatur durch die Shopbetreiber. Angaben zu diesem DRM finden Sie auf den Seiten der jeweiligen Anbieter.
Für Karin – E.G.
Für Sylvie – R.H.
Für Faith – R.J.
Für Dru Ann und Matthew Josua 24:15b
Dieses Buch wird Ihnen keine Einführung in die objektorientierte Programmierung oder das Anwendungsdesign als solches bieten – denn immerhin gibt es bereits zahlreiche sehr gute Nachschlagewerke, die diese Themen ausführlich behandeln. Die Inhalte dieses Buches setzen vielmehr voraus, dass Sie mindestens eine objektorientierte Programmiersprache relativ gut beherrschen und über grundlegende Erfahrungen im objektorientierten Design verfügen, so dass Sie nicht zum nächstbesten Fachlexikon greifen müssen, wenn Begriffe wie »Typen«, »Polymorphie« oder »Schnittstellenvererbung« statt »Implementierungsvererbung« zur Sprache kommen.
Andererseits handelt es sich bei diesem Buch aber auch nicht um eine technische Abhandlung für fortgeschrittene Entwickler. Es befasst sich mit Design Patterns, sprich Entwurfsmustern, die einfache und elegante Lösungen für spezifische Problemstellungen im objektorientierten Softwaredesign anbieten. Design Patterns repräsentieren im Grunde genommen Problemlösungen, die sich in der Praxis als sinnvoll und hilfreich erwiesen haben. Sie beschreiben Designs, die man normalerweise nicht ohne Weiteres in dieser Art entwickelt. Die Patterns basieren auf zahllosen Design- und Programmrevisionen, die Softwareentwickler auf der Suche nach besseren Wiederverwendungsmöglichkeiten und größerer Flexibilität im Laufe der Zeit erarbeitet haben – und sie stellen diese Lösungen in einer konzentrierten und einfach anzuwendenden Form zur Verfügung.
Die in dem hier vorgestellten Katalog enthaltenen Patterns erfordern weder außergewöhnliche programmiersprachliche Funktionen noch irgendwelche raffinierten Programmiertricks, mit denen man Freunde und Vorgesetzte beeindrucken kann. Sie können allesamt in den standardmäßigen objektorientierten Programmiersprachen implementiert werden, sind allerdings mit etwas mehr Aufwand verbunden als Ad-hoc-Lösungen – dieser Mehraufwand wird jedoch immer mit einer deutlich besseren Wiederverwendbarkeit und höherer Flexibilität belohnt.
Wer die Arbeitsweise der Design Patterns erst einmal verstanden hat und diesbezüglich zu einem »Aha!«-Erlebnis gelangt ist, wird fortan völlig anders über das objektorientierte Design denken. Die Patterns vermitteln Ihnen Einsichten, die es Ihnen ermöglichen, Ihre Designs flexibler, modularer, wiederverwendbarer und verständlicher zu gestalten – und genau das zählt schließlich zu den Hauptgründen, sich der objektorientierten Programmierung zuzuwenden, richtig?
An dieser Stelle sei aber auch gleich angemerkt: Machen Sie sich keine Gedanken, wenn Sie den Inhalt dieses Buches nicht schon bei der ersten Lektüre vollumfänglich verstehen. Selbst wir Autoren haben nicht immer alle Zusammenhänge dessen, was wir da zu Papier gebracht haben, sofort zu 100 % verstanden! Dieses Buch ist kein »Schmöker«, den man einmal liest und dann ins Bücherregal stellt – es ist im wahrsten Sinne des Wortes ein Nachschlagewerk, von dem wir hoffen, dass Sie es zukünftig immer wieder aufschlagen werden, um neue Erkenntnisse und Anregungen für Ihre eigenen Designs zu gewinnen.
Die Fertigstellung dieses Buches hat sich recht lange hingezogen. Es hat sozusagen vier Länder »bereist«, die Hochzeiten von drei seiner Autoren und sogar die Geburten von zwei (nicht miteinander verwandten) Sprösslingen erlebt. Und es waren sehr viele Menschen an der Entstehung dieses Werkes beteiligt. Besonderer Dank gebührt in diesem Zusammenhang Bruce Anderson, Kent Beck und André Weinand für ihre Inspiration und ihre hilfreichen Ratschläge. Unser Dank richtet sich auch an all jene, die unsere Manuskriptentwürfe gelesen und kritisch kommentiert und bewertet haben: Roger Bielefeld, Grady Booch, Tom Cargill, Mashall Cline, Ralph Hyre, Brian Kernighan, Thomas Laliberty, Mark Lorenz, Arthur Riel, Doug Schmidt, Clovis Tondo, Steve Vinoski und Rebecca Wirfs-Brock. Weiterhin danken wir dem Team von Addison-Wesley für seine Hilfe und Geduld: Kate Habib, Tiffany Moore, Lisa Raffaele, Pradeepa Siva und John Wait. Ein ganz besonderer Dank geht an Carl Kessler, Danny Sabbah und Mark Wegman von IBM Research für ihre unermüdliche Unterstützung für dieses Projekt.
Selbstverständlich danken wir auch all den Menschen, die uns per Internet und auf anderen Wegen ihre Erfahrungen mit und Meinungen zu den verschiedenen Patterns mitgeteilt haben, uns ermutigt haben und uns wissen ließen, dass unsere Arbeit die Mühe wert war. Stellvertretend für all diese Menschen bedanken wir uns an dieser Stelle bei Jon Avotins, Steve Berczuk, Julian Berdych, Matthias Bohlen, John Brant, Allan Clarke, Paul Chisholm, Jens Coldewey, Dave Collins, Jim Coplien, Don Dwiggins, Gabriele Elia, Doug Felt, Brian Foote, Denis Fortin, Ward Harold, Hermann Hueni, Nayeem Islam, Bikramjit Kalra, Paul Keefer, Thomas Kofler, Doug Lea, Dan LaLiberte, James Long, Ann Louise Luu, Pundi Madhavan, Brian Marick, Robert Martin, Dave McComb, Carl McConnell, Christine Mingins, Hanspeter Mössenböck, Eric Newton, Marianne Ozkan, Roxsan Payette, Larry Podmolik, George Radin, Sita Ramakrishnan, Russ Ramirez, Alexander Ran, Dirk Riehle, Bryan Rosenburg, Aamod Sane, Duri Schmidt, Robert Seidl, Xin Shu und Bill Walker.
Wir betrachten den in diesem Buch vorgestellten Design-Pattern-Katalog keineswegs als vollständig und unveränderlich – er repräsentiert vielmehr eine Momentaufnahme unserer Überlegungen und Erwägungen in Bezug auf gutes Softwaredesign. Wir freuen uns über Meinungen und Kommentare jeder Art, sei es Kritik oder Lob zu unseren Beispielen, Hinweise auf Referenzen und Praxisbeispiele, die wir übersehen haben, oder Vorschläge für weitere Patterns, die wir ebenfalls in den Katalog hätten aufnehmen sollen. Ihre diesbezüglichen Anfragen erreichen uns per E-Mail an [email protected]. Die in diesem Buch verwendeten Codebeispiele stehen unter www.mitp.de/9700 zum Download zur Verfügung oder können alternativ per E-Mail mit dem Inhalt »send design pattern source« an design-patterns-sourceics.uiuc.edu bei uns angefordert werden.
E.G., Mountain View, California
R.H., Montreal, Quebec
R.J., Urbana, Illinois
J.V., Hawthorne, New York
August 1994
Alle gut strukturierten objektorientierten Architekturen basieren auf Mustern, auf Englisch »Patterns« genannt. Ich persönlich bemesse die Qualität eines objektorientierten Systems im Prinzip daran, wie viel Sorgfalt die Entwickler der grundsätzlich möglichen Zusammenarbeit der einzelnen Objekte gewidmet haben. Konzentriert man sich bei der Systementwicklung vorranging auf diese Mechanismen, dann gelangt man letztendlich zu Architekturen, die kompakter, schlichter und erheblich besser zu verstehen sind, als wenn diese Muster außer Acht gelassen werden.
Die Bedeutung von Patterns für die Erstellung komplexer Systeme wurde in anderen Disziplinen schon längst erkannt. Allen voran der Architekturtheoretiker Christopher Alexander und seine Mitarbeiter gehörten zu den Ersten, die die Etablierung und Instrumentalisierung einer »Pattern Language« (dt. »Muster-Sprache«) für die Konstruktion von Gebäuden und Städten vorschlugen. Seine Ideen und die Beiträge weiterer seiner Mitstreiter haben inzwischen auch in der objektorientierten Software Community Fuß gefasst. Kurzum: Das Konzept der Design Patterns verkörpert in der Programmentwicklung das Schlüsselelement, um sich die Erfahrungen und das Wissen anderer talentierter (Software-)Architekten zunutze zu machen.
Erich Gamma, Richard Helm, Ralph Johnson und John Vlissides erläutern in diesem Buch die Grundsätze der Design Patterns und stellen einen Katalog solcher Muster vor. Damit leistet dieses Nachschlagewerk zwei wichtige Beiträge: Zum einen veranschaulicht es nachdrücklich die Rolle, die Patterns bei der Entwicklung komplexer Systeme einnehmen können. Und zum anderen dient es als äußerst praktische Referenz für eine Sammlung wohldurchdachter Patterns, die praktizierende Entwickler bei der Gestaltung ihrer eigenen spezifischen Anwendungen nutzen können.
Ich bin stolz darauf, dass mir die Ehre zuteilwurde, mit einigen der Autoren dieses Buches unmittelbar an ihren architektonischen Designbemühungen zusammenarbeiten zu dürfen. Ich habe viel von ihnen gelernt und bin überzeugt, dass es Ihnen bei der Lektüre dieses Buches ebenso ergehen wird.
Grady Booch
Chief Scientist, Rational Software Corporation
Dieses Buch gliedert sich im Wesentlichen in zwei Teile. Der erste Teil umfasst die Kapitel 1 und 2 und beschreibt, was Design Patterns sind und wie sie Ihnen bei der Entwicklung objektorientierter Software von Nutzen sein können. Zudem wird ihre praktische Anwendung anhand einer ausführlichen Fallstudie demonstriert. Im zweiten Teil des Buches (die Kapitel 3, 4 und 5) wird dann der eigentliche Katalog der einzelnen Design Patterns präsentiert.
Dieser Katalog macht den größten Teil des Buches aus. Die zugehörigen Kapitel 3, 4 und 5 unterteilen die Design Patterns in drei Kategorien: Erzeugungsmuster (Creational Patterns), Strukturmuster (Structural Patterns) und Verhaltensmuster (Behavorial Patterns).
Sie können den Katalog auf verschiedene Art und Weise nutzen: Entweder lesen Sie ihn von Anfang bis Ende durch oder wechseln von Pattern zu Pattern. Oder aber Sie konzentrieren sich jeweils auf ein Kapitel, um zu ergründen, inwiefern sich die Patterns ein und derselben Kategorie voneinander unterscheiden.
Die in den einzelnen Kapiteln angegebenen Querverweise zwischen den Patterns bilden einen roten Faden durch den Katalog. Auf diese Weise können Sie sich bequem einen Überblick darüber verschaffen, in welcher Beziehung die Patterns zueinander stehen, wie sie kombiniert werden können und welche Design Patterns gut zusammenarbeiten. Die verwandtschaftlichen Beziehungen der einzelnen Design Patterns sind in Abbildung 1.2 in einer Übersicht dargestellt.
Wenn Sie möchten, können Sie beim Lesen des Katalogs aber auch problemorientiert vorgehen. So könnten Sie z. B. gleich mit dem Abschnitt 1.6 beginnen, um sich über diverse allgemeine Probleme hinsichtlich des Designs wiederverwendbarer objektorientierter Software zu informieren, und anschließend die Ausführungen zu den Design Patterns lesen, die für die jeweiligen Problemstellungen geeignet sind. Oder Sie arbeiten zuerst den gesamten Katalog durch und gehen danach problemorientiert vor, um die passenden Patterns in Ihren Projekten anzuwenden.
Wenn Sie auf dem Gebiet der objektorientierten Entwicklung noch nicht so viel Erfahrung haben, empfiehlt es sich, mit den einfachsten und geläufigsten Patterns anzufangen:
Abstract Factory (Abstrakte Fabrik, siehe Abschnitt 3.1)
Adapter (Adapter, siehe Abschnitt 4.1)
Composite (Kompositum, siehe Abschnitt 4.3)
Decorator (Dekorierer, siehe Abschnitt 4.4)
Factory Method (Fabrikmethode, siehe Abschnitt 3.3)
Observer (Beobachter, siehe Abschnitt 5.7)
Strategy (Strategie, siehe Abschnitt 5.9)
Template Method (Schablonenmethode, siehe Abschnitt 5.10)
Kaum ein objektorientiertes System kommt ohne wenigstens ein paar Patterns aus – große Systeme nutzen sogar nahezu alle der hier genannten Exemplare. Diese Auswahl wird Ihnen helfen, die Design Patterns im Besonderen und das Konzept eines guten objektorientierten Designs im Allgemeinen wirklich zu verstehen.
Das Entwickeln bzw. Entwerfen objektorientierter Software ist zweifellos kein leichtes Unterfangen – und das Designen wiederverwendbarer objektorientierter Software gestaltet sich sogar noch anspruchsvoller und komplexer: Neben der Bestimmung der relevanten Objekte und deren Abstrahierung zu Klassen in geeigneter Granularität müssen außerdem passende Schnittstellen und Vererbungshierarchien definiert sowie die zentralen Beziehungen zwischen den einzelnen Klassen bestimmt werden. Darüber hinaus sollte sich das Softwaredesign natürlich einerseits speziell an den jeweiligen Erfordernissen orientieren, andererseits aber gleichermaßen eine hinreichende Universalität aufweisen, um auch für zukünftige Problemstellungen und Anforderungen gewappnet zu sein. Und Designrevisionen sollten nach Möglichkeit ebenfalls vermieden oder zumindest auf ein Minimum reduziert werden.
Erfahrene objektorientierte Programmierer sind sich im Allgemeinen darüber im Klaren, dass es sehr schwierig, wenn nicht gar unmöglich ist, ein wiederverwendbares, flexibles Design gleich von Anfang an »richtig« hinzubekommen. Aus diesem Grund greifen sie häufig mehrfach auf ihre früheren Entwürfe zurück und versuchen zunächst, weitere Modifikationen zu implementieren, bevor sie ein Softwaredesign endgültig als »abgeschlossen« betrachten.
Das Erlernen und Verinnerlichen der maßgeblichen Aspekte, die ein gutes objektorientiertes Design ausmachen, erfordert viel Zeit und Geduld. Folglich fällt es erfahrenen objektorientierten Programmierern in der Regel leichter, gute Designs zu entwerfen, als weniger geübten Entwicklern, die sich nicht selten von den zahlreichen Designoptionen überfordert fühlen und daher oft doch lieber die nicht objektorientierten Techniken anwenden, die sie früher schon eingesetzt haben. Erfahrenen Programmierern ist also offenbar spezielles Wissen zu eigen, über das unerfahrene Entwicklern (noch) nicht verfügen. Doch worum genau handelt es sich dabei?
Nun, versierten Softwaredesignern ist beispielsweise bewusst, dass sie nicht ständig versuchen müssen, für jedes Problem eine völlig neue Lösung zu entwickeln. Stattdessen machen sie sich Lösungsmöglichkeiten zunutze, die sich nach ihrer persönlichen Erfahrung bereits bewährt haben – mit anderen Worten: Haben sie erst einmal gute Lösungsansätze gefunden, dann »recyceln« sie diese und wenden sie immer wieder an. Dieser Erfahrungsvorsprung zeichnet die Experten im Bereich des Softwaredesigns aus – und erklärt auch, warum viele objektorientierte Systeme häufig wiederkehrende Klassenmuster und kommunizierende Objekte enthalten. Solche Muster (engl. Patterns) beheben bestimmte Designprobleme und gestalten objektorientierte Softwareentwürfe nicht nur flexibler und eleganter, sondern gestatten letztlich überhaupt erst ihre Wiederverwendbarkeit. Sie ermöglichen den Entwicklern, neue Designs auf gelungenen Entwürfen aufzusetzen und darauf aufzubauen. Kurz gesagt: Softwaredesigner, die mit Patterns vertraut sind, können diese unmittelbar auf die jeweils vorliegenden Problemstellungen anwenden, ohne erst »das Rad neu erfinden« zu müssen.
Ein vergleichbares Prinzip findet auch in anderen kreativen Prozessen Anwendung. So erarbeiten beispielsweise Roman- und Bühnenautoren die Handlungen ihrer Geschichten und Theaterstücke nur höchst selten von Grund auf – vielmehr bedienen sie sich in den meisten Fällen ebenfalls eines bewährten »Musters« bzw. Leitmotivs wie etwa »tragische Heldenfigur« (Macbeth, Hamlet etc.) oder »romantische Erzählung« (zahllose Liebesromane). In ähnlicher Weise setzen auch die objektorientierten Programmierer Entwurfsmuster (engl. Design Patterns) wie etwa »Zustände werden durch Objekte repräsentiert« oder »Objekte werden zwecks einfacher Ergänzung/Entfernung von Eigenschaften dekoriert« ein. Und steht das Muster erst einmal fest, ergeben sich viele weitere Designentscheidungen ganz automatisch.
Als Entwickler wissen wir alle den Wert der praktischen Designerfahrung zu schätzen. Haben Sie beim Betrachten eines Softwaredesigns nicht auch schon mal ein Déjà-vu-Erlebnis gehabt – das Gefühl, ein bestimmtes Problem in der Vergangenheit bereits bewältigt zu haben, ohne dass Sie sich daran erinnern könnten, wie genau oder in welchem Zusammenhang? Wenn Ihnen die exakte Problemstellung bzw. der seinerzeit eingeschlagene Lösungsweg wieder einfallen würde, könnten Sie auf diese Erfahrung zurückgreifen, statt ganz von vorn anfangen zu müssen. Leider werden die im Laufe eines objektorientierten Designprozesses gewonnenen Erkenntnisse in der Regel jedoch nicht so umfassend protokolliert, dass auch Dritte davon profitieren könnten.
Deshalb lautet die Zielsetzung dieses Buches, besagten Erkenntnisgewinn anhand von sogenannten Design Patterns (auch Entwurfsmuster oder kurz Patterns genannt) zu dokumentieren, damit er für jedermann zugänglich und effizient nutzbar wird. Zu diesem Zweck haben wir die wichtigsten Patterns in Katalogform zusammengefasst, wobei jedes Muster systematisch je ein bedeutsames wiederkehrendes Design benennt, beschreibt und evaluiert.
Design Patterns erleichtern nicht nur die Wiederverwendung erfolgreicher Softwaredesigns und -architekturen, sondern bieten Entwicklern zudem auch einen besseren, ungehinderten Zugang zu bewährten Techniken. Außerdem dienen sie als Entscheidungshilfe bei der Wahl möglicher Designalternativen zwecks Gewährleistung der Wiederverwendbarkeit eines Systems und verhindern gleichzeitig Alternativen, die einer Wiederverwendung entgegenstehen. Im Übrigen können Patterns ebenfalls dazu beitragen, die Dokumentation und Wartung bereits existenter Systeme zu verbessern, indem sie explizite Spezifikationen für die Klassen- und Objektinteraktionen sowie deren Intention bereitstellen. Unterm Strich heißt das: Design Patterns unterstützen Programmierer dabei, ihre Softwaredesigns schneller »richtig« hinzubekommen.
Keins der in diesem Buch beschriebenen Patterns ist vollkommen neu oder unerprobt. Im Gegenteil: Es werden ausschließlich solche Design Patterns verwendet, die schon mehrfach in verschiedenen Systemen eingesetzt wurden. Die meisten von ihnen wurden allerdings noch nie zuvor dokumentiert, sondern entstammen entweder dem gemeinschaftlichen Fundus der objektorientierten Community oder erfolgreichen objektorientierten Systemen – also zwei Bereichen, die sich unerfahrenen Programmierern nicht so leicht erschließen. Aber auch wenn die vorgestellten Patterns nicht brandneu sind, werden sie doch in einer neuartigen, leicht verständlichen Art und Weise vorgestellt: in Form eines Design-Pattern-Katalogs, der einer einheitlichen Präsentationskonvention folgt.
Aufgrund des naturgemäß begrenzten Platzangebots in diesem Buch repräsentieren die hier erläuterten Muster lediglich einen Bruchteil des Pattern-Fundus, der echten Programmierexperten zur Verfügung steht. So werden beispielsweise keine Design Patterns für die nebenläufige, die verteilte oder die Echtzeitprogrammierung oder für spezifische Anwendungsdomänen verwendet. Ebenso wenig werden Sie in diesem Buch Informationen zur Entwicklung von Benutzeroberflächen, zum Schreiben von Gerätetreibern oder zum Einsatz objektorientierter Datenbanken vorfinden. Für all diese Aufgabenbereiche existieren wiederum spezielle Design Patterns – die es jedoch sicherlich ebenfalls wert wären, eigens katalogisiert zu werden.
Der US-amerikanische Architekturtheoretiker Christopher Alexander erklärt in seinem Buch »Eine Muster-Sprache« [Löcker Verlag, Wien, 1995, Seite x]: »Jedes Muster beschreibt zunächst ein in unserer Umwelt immer wieder auftretendes Problem, beschreibt dann den Kern der Lösung dieses Problems, und zwar so daß man diese Lösung millionenfach anwenden kann, ohne sich je zu wiederholen.« Natürlich bezieht sich Alexander in diesem Fall auf Muster, die sich in Gebäuden und Stadtplanungsentwürfen wiederfinden, dennoch trifft seine Definition auch auf objektorientierte Design Patterns zu – mit dem Unterschied, dass die Lösungen hier nicht in Form von Wänden und Türen, sondern von Objekten und Schnittstellen ausgedrückt werden. Prinzipiell repräsentieren jedoch beide Musterspezies Problemlösungen für bestimmte Situationen in bestimmten Kontexten.
Ein Design Pattern umfasst im Wesentlichen vier maßgebliche Elemente:
Der kurze, meist aus ein oder zwei Wörtern bestehende Pattern-Name dient zur Referenzierung des jeweiligen Designproblems sowie der zugehörigen Lösungen und deren Auswirkungen. Durch die Benennung eines Design Patterns erweitern wir unser designbezogenes Vokabular in der Art, dass ein Arbeiten auf einer höheren Abstraktionsebene möglich wird – denn dieses Vokabular erleichtert die Dokumentation des Softwaredesigns und vor allem auch die Kommunikation mit Kollegen und Dritten, z. B. zur Erörterung der Vor- und Nachteile neuer Ideen und Vorschläge, in erheblichem Maße. Wirklich geeignete Namen zu finden, ist allerdings keine leichte Aufgabe – das zeigt auch die Tatsache, dass die Vergabe passender Bezeichnungen (im Englischen wie im Deutschen) für die in diesem Buch vorgestellten Design Patterns bei der Entwicklung unseres Katalogs eine der größten Herausforderungen überhaupt darstellte.
Die Problemstellung beschreibt die Situation, in der das Design Pattern anzuwenden ist. Sie erläutert die jeweils vorliegende Problematik sowie deren Kontext. Dabei kann es sich um spezifische Designprobleme handeln, wie z. B. in welcher Form Algorithmen als Objekte abgebildet werden sollten, aber auch um für unflexible Designs symptomatische Klassen- oder Objektstrukturen. Mitunter werden im Rahmen der Problembeschreibung außerdem bestimmte Bedingungen aufgeführt, die erfüllt sein müssen, damit der Einsatz des Design Patterns überhaupt sinnvoll ist.
Die Lösung berücksichtigt neben den konkreten designbildenden Elementen auch deren Beziehungen zueinander, ihre Zuständigkeiten sowie ihre Interaktionen. Sie definiert allerdings kein bestimmtes Design oder eine konkrete Implementierung, denn ein Pattern entspricht eher einer Art Schablone, die in vielen verschiedenen Situationen anwendbar ist: Es skizziert eine abstrakte Beschreibung eines Designproblems und zeigt auf, wie dieses durch eine generelle Anordnung von Elementen (in unserem Fall Klassen und Objekten) bewältigt werden kann.
Die Konsequenzen bilden eine Übersicht der Auswirkungen und Kompromisse ab, die sich aus der Anwendung des Patterns ergeben. Leider bleiben sie, obwohl sie für die Bewertung möglicher Designalternativen und das Abwägen ihrer Vor- und Nachteile von entscheidender Bedeutung sind, in der Argumentation für eine Designentscheidung häufig unerwähnt.
In der Softwareentwicklung stehen die Konsequenzen eines Pattern-Einsatzes oftmals in direktem Zusammenhang mit dem Speicherplatzbedarf und den Ausführungszeiten, sie können aber ebenso gut auch Sprach- und andere Implementierungsaspekte betreffen. Und da die Wiederverwendbarkeit in der objektorientierten Programmierung eine wichtige Rolle spielt, schließt dies selbstverständlich auch den Einfluss des infrage stehenden Design Patterns auf die Flexibilität, Erweiterbarkeit und Portabilität des Systems mit ein. Generell gilt also: Die ausdrückliche, sachliche Erwägung der zu erwartenden Konsequenzen ist für die lückenlose Evaluierung eines Patterns unverzichtbar.
Die Interpretation des tatsächlichen Nutzwertes eines Design Patterns hängt im Endeffekt immer auch von der Perspektive des Betrachters ab: Während ein Pattern für den einen Entwickler besonders hilfreich erscheinen mag, kann es für einen anderen Programmierer bloß einen primitiven Baustein darstellen. Bei der Zusammenstellung des Design-Pattern-Katalogs haben wir uns vordergründig auf eine bestimmte Abstraktionsebene konzentriert. Die Patterns verkörpern keine Entwürfe für verkettete Listen oder Hashtabellen, die als separate Klassen programmiert und dann im Ist-Zustand wiederverwendet werden können. Ebenso wenig stellen sie komplexe, domänenspezifische Designs für eine komplette Anwendung oder ein Teilsystem dar. Vielmehr handelt es sich bei den hier beschriebenen Design Patterns um Darstellungen kommunizierender Objekte und Klassen, die auf die Lösung eines allgemeinen Designproblems in einem speziellen Kontext zugeschnitten sind.
Ein Design Pattern benennt, abstrahiert und identifiziert die Schlüsselaspekte einer allgemeinen Designstruktur, durch die es sich für die Entwicklung eines wiederverwendbaren objektorientierten Designs auszeichnet. Es identifiziert die beteiligten Klassen und Instanzen, deren Rollen und Interaktionen sowie die ihnen zugedachte Aufgabenverteilung. Jedes Pattern eignet sich für eine ganz bestimmte objektorientierte Designproblematik und besitzt spezielle Merkmale, die Aufschluss darüber geben, wann seine Anwendung zweckmäßig ist, ob es trotz möglicher anderer designbedingten Einschränkungen überhaupt eingesetzt werden kann und welche Konsequenzen und Vor- und Nachteile es mit sich bringt. Und weil die Design Patterns schlussendlich natürlich implementiert werden müssen, ist zu jedem von ihnen auch ein Codebeispiel in C++ und (mitunter) Smalltalk angegeben, das eine mögliche Implementierungsvariante aufzeigt.
Auch wenn Patterns prinzipiell objektorientierte Designs beschreiben, basieren sie dennoch auf praktischen Lösungen, die in etablierten objektorientierten Programmiersprachen wie Smalltalk und C++ statt in prozeduralen (Pascal, C, Ada) oder dynamischen objektorientierten Sprachen (CLOS, Dylan, Self) implementiert sind. In diesem Buch haben wir uns aus rein pragmatischen Gründen für die Verwendung von Smalltalk und C++ entschieden, weil wir selbst tagtäglich mit diesen Programmiersprachen arbeiten.
Die Wahl der Programmiersprache spielt insofern eine gewichtige Rolle, als sie den eigenen Blickwinkel beeinflusst. Die in diesem Buch vorgestellten Patterns setzen Sprachfunktionen auf Smalltalk- bzw. C++-Niveau voraus, die somit auch bestimmen, was leicht bzw. was weniger leicht implementiert werden kann. Würden dagegen prozedurale Sprachen zugrunde gelegt, müsste der Katalog ggf. um weitere Design Patterns, z. B. der Art »Inheritance« (Vererbung), »Encapsulation« (Kapselung) oder »Polymorphism« (Polymorphie), ergänzt werden. Außerdem gilt für manche der weniger populären objektorientierten Sprachen, dass diese die Funktionalität einiger unserer Patterns bereits von Haus aus unterstützen. So sind in CLOS beispielsweise Multimethoden verfügbar, die den Bedarf für ein Design Pattern wie Visitor (Besucher, siehe Abschnitt 5.11) verringern. Aber auch Smalltalk und C++ unterscheiden sich in vielerlei Hinsicht so weit voneinander, dass sich manche Design Patterns in der einen Sprache einfacher ausdrücken lassen als in der anderen (siehe zum Beispiel Iterator (Iterator, Abschnitt 5.4)).
In Smalltalk-80 wird zur Entwicklung von Benutzeroberflächen das aus den drei Komponenten Model (Datenmodell), View (Präsentation) und Controller (Programmsteuerung) bestehende MVC-Architekturschema eingesetzt. Zur Verdeutlichung des Konzepts der »Patterns« sollen die in MVC verwendeten Design Patterns an dieser Stelle einmal etwas eingehender betrachtet werden.
Das MVC-Architekturschema besteht aus drei Objektarten bzw. -schichten: Das Model-Objekt entspricht dem Anwendungsobjekt, das View-Objekt repräsentiert dessen Bildschirmdarstellung und das Controller-Objekt steuert die Reaktionen der Benutzeroberfläche auf userseitige Eingaben. Vor der Einführung des MVC-Schemas wurden diese Objekte in Benutzeroberflächendesigns tendenziell einfach in einem einzigen Objekt zusammengefasst. In MVC werden sie dagegen separat verwendet, wodurch eine bessere Flexibilität und Wiederverwendbarkeit gewährleistet ist.
Das MVC-Architekturmuster nutzt ein Subscribe/Notify-Benachrichtigungsprotokoll, um die View- und Model-Objekte zu entkoppeln und sicherzustellen, dass die View-Objekte stets den aktuellen Zustand ihres zugehörigen Model-Objekts abbilden. Zu diesem Zweck werden Änderungen an den Daten im Model-Objekt an die jeweils abhängigen View-Objekte kommuniziert, damit sie sich entsprechend aktualisieren können. Dieser Ansatz ermöglicht die Anbindung mehrerer View-Objekte an ein Model-Objekt, um verschiedene Präsentationen anzubieten, und gestattet zudem die Erzeugung neuer View-Objekte, ohne das Model-Objekt umschreiben zu müssen.
Abbildung 1.1 zeigt ein Model-Objekt mit drei zugehörigen View-Objekten. (Der Einfachheit halber wurden die Controller-Objekte in diesem Beispiel weggelassen.) Das Model-Objekt enthält einige Daten, die mittels der View-Objekte in verschiedenen Ausgabeformaten wiedergegeben werden: als Tabelle, als Histogramm und als Tortendiagramm. Im Fall einer Datenänderung benachrichtigt das Model-Objekt die View-Objekte entsprechend, woraufhin diese auf die geänderten Daten zugreifen und die nötigen Anpassungen vornehmen.
Oberflächlich betrachtet, demonstriert dieses Pattern-Beispiel lediglich ein Design, bei dem die View-Objekte und das Model-Objekt entkoppelt werden. Auf den zweiten Blick lässt es jedoch ein weitaus umfassenderes Konzept erkennen: die Separierung von Objekten in der Form, dass sich Änderungen an einem Objekt auf eine beliebige Anzahl anderer Objekte auswirken können, ohne dass das geänderte Objekt die genaue Beschaffenheit der anderen Objekte berücksichtigen muss. Dieses allgemeinere Design wird durch das Design Pattern Observer (Beobachter, siehe Abschnitt 5.7) beschrieben.
Abb. 1.1: MVC-Design-Pattern-Beispiel
Ein weiteres Merkmal des MVC-Architekturschemas ist außerdem die Schachtelung von View-Objekten. So könnte beispielsweise ein mit Buttons bestückter Dialog als ein komplexes View-Objekt implementiert werden, das wiederum aus geschachtelten Button-View-Objekten besteht. Oder die Oberfläche eines Objektinspektors könnte aus geschachtelten View-Objekten bestehen, die sich im Debugger wiederverwenden lassen. Das MVC-Architekturschema unterstützt geschachtelte View-Objekte durch die Klasse CompositeView, eine Unterklasse von View. CompositeView-Objekte verhalten sich genauso wie View-Objekte und können überall dort verwendet werden, wo auch Views einsetzbar sind – darüber hinaus enthalten und verwalten sie aber auch geschachtelte View-Objekte.
Nun könnte man sich darunter einfach ein Design vorstellen, das es ermöglicht, einen CompositeView genauso zu behandeln wie eine seiner Komponenten. Dieses Design begegnet jedoch zusätzlich noch einer allgemeineren Problematik, die eintritt, wenn man Objekte zu einer Gruppe zusammenfassen und diese Gruppe dann wie ein individuelles Objekt behandeln möchte. Ein derartiges universeller einsetzbares Design wird durch das Design Pattern Composite (Kompositum, siehe Abschnitt 4.3) beschrieben: Es ermöglicht die Erstellung einer Klassenhierarchie, in der einige Unterklassen primitive Objekte (wie z. B. Schaltflächen) definieren und andere Klassen wiederum die Composite-Objekte (CompositeView), die die primitiven Objekte zu komplexeren Objekten zusammenfügen.
Zudem gestattet das MVC-Architekturmuster auch die Steuerung der Reaktion eines View-Objekts auf eine Benutzereingabe, ohne dessen visuelle Präsentation zu modifizieren. So ließe sich beispielsweise die Reaktion des View-Objekts auf Tastatureingaben verändern oder statt Tastenbefehlen ein entsprechendes Popup-Menü nutzen. MVC kapselt den Reaktionsmechanismus in sogenannten Controller-Objekten, die einer Klassenhierarchie unterliegen, welche die Erstellung neuer Variationen eines bereits vorhandenen Controller-Objekts erleichtert.
Zur Implementierung einer bestimmten Reaktionsstrategie nutzen View-Objekte eine Instanz einer Controller-Unterklasse. Soll also eine andere Strategie implementiert werden, wird die Instanz einfach durch ein anderes Controller-Objekt ersetzt. Es ist sogar möglich, den Controller eines View-Objekts zur Laufzeit zu ändern, um die Reaktion des Views auf User-Eingaben zu modifizieren. Wollte man beispielsweise erreichen, dass ein View-Objekt nicht mehr auf Eingaben reagiert, sprich deaktiviert wird, müsste man ihm lediglich ein Controller-Objekt zuweisen, das Eingabeereignisse ignoriert.
Die Beziehung zwischen View- und Controller-Objekt ist ein Beispiel für das Design Pattern Strategy (Strategie, siehe Abschnitt 5.9). Ein Strategieobjekt ist ein Objekt, das einen Algorithmus repräsentiert. Es ist insbesondere dann nützlich, wenn ein Algorithmus strategisch oder dynamisch ersetzt werden soll, zahlreiche Varianten des Algorithmus vorliegen oder der Algorithmus komplexe Datenstrukturen enthält, die gekapselt werden sollen.
Darüber hinaus setzt das MVC-Architekturschema auch andere Design Patterns ein, wie z. B. Factory Method (Fabrikmethode, siehe Abschnitt 3.3) zur Spezifikation der Standard-Controller-Klasse eines View-Objekts oder auch Decorator (Dekorierer, siehe Abschnitt 4.4), um ein View-Objekt mit Scrollfähigkeit auszustatten – die wichtigsten Beziehungen im MVC-Architekturschema werden im Allgemeinen jedoch durch die Design Patterns Observer (Beobachter, siehe Abschnitt 5.7), Composite (Kompositum, siehe Abschnitt 4.3) und Strategy (Strategie, siehe Abschnitt 5.9) definiert.
Wie lassen sich Design Patterns beschreiben? Grafische Notationen, die zwar ebenso wichtig wie hilfreich sind, reichen hier nicht aus, denn sie erfassen das Endprodukt des Designprozesses lediglich als Beziehungen zwischen Klassen und Objekten. Zur Gewährleistung der Wiederverwendbarkeit des Softwaredesigns müssen jedoch auch die Entscheidungen, Alternativen und Erwägungen, die letztlich zu ihm geführt haben, aufgezeichnet werden. Hilfreich sind in diesem Zusammenhang außerdem konkrete Beispiele, die die Arbeitsweise des Designs verdeutlichen.
Die Beschreibung der Design Patterns erfolgt in diesem Buch nach einem einheitlichen Schema, das jedes Pattern in bestimmte Eigenschaften gliedert. Auf diese Weise ist eine gleichmäßige Informationsstruktur gewährleistet, die das Erlernen, Vergleichen und Anwenden der Design Patterns erleichtert.
Die Bezeichnung des Design Patterns charakterisiert zugleich dessen Hauptfunktion bzw. -eigenschaft. Ein aussagekräftiger, prägnanter Name spielt für den täglichen Sprachgebrauch in der Designarbeit eine ganz entscheidende Rolle, um eindeutigen Bezug auf das Pattern nehmen zu können. Die Klassifizierung des Design Patterns erfolgt nach dem in Abschnitt 1.5 beschriebenen System.
Die Zweckbeschreibung des Patterns fasst die Antworten auf folgende Fragen in knapper Form zusammen: Was bewirkt das Design Pattern? Welchem Grundprinzip folgt es und welche Intention steht dahinter? Auf welche spezifischen Designfragestellungen oder -probleme ist es ausgerichtet?
Hier sind weitere Bezeichnungen angegeben, unter denen das Pattern ggf. auch bekannt ist.
Zum besseren Verständnis der im weiteren Verlauf dieses Buches beschriebenen abstrakteren Eigenschaften des Design Patterns wird je ein Beispielszenario für eine zugrunde liegende Designproblematik angeführt, das aufzeigt, inwiefern die Klassen- und Objektstrukturen des Patterns das Problem lösen.
In welchen Situationen lässt sich das Design Pattern nutzbringend einsetzen? Welche Designprobleme lassen sich damit beheben? Wie erkennt man solche Situationen?
Die grafische Darstellung der in dem Design Pattern enthaltenen Klassen basiert auf der Object-Modeling Technique (OMT)-Notation [RBP+91]. Darüber hinaus werden zur Veranschaulichung von Abfrage- und Interaktionsabfolgen zwischen Objekten auch diverse Interaktionsdiagramme [JCJO92, Boo94] dargestellt. Diese Notationen sind in Anhang B beschrieben.
Hier werden die an einem Design Pattern beteiligten Klassen und/oder Objekte sowie deren Zuständigkeiten erläutert.
An dieser Stelle wird beschrieben, in welcher Weise die Teilnehmer zwecks Ausübung ihrer Zuständigkeiten interagieren.
In welcher Weise wird die Zielsetzung des Design Patterns unterstützt? Welche Vor- und Nachteile bzw. Resultate ergeben sich aus der Anwendung des Patterns? Welche Bereiche der Systemstruktur lassen sich unabhängig voneinander variieren?
Welche Stolperfallen, Tipps oder Techniken sollten bei der Implementierung des Design Patterns beachtet werden? Könnten sprachspezifische Probleme auftreten?
Die Codefragmente demonstrieren, wie sich das Design Pattern in C++ oder Smalltalk implementieren lässt.
Zur Demonstration des praktischen Einsatzes des Design Patterns in realen Systemen werden mindestens zwei Beispiele aus unterschiedlichen Anwendungsbereichen angeführt.
Welche anderen Design Patterns stehen in einem engeren Bezug zu dem aktuellen Pattern? Worin bestehen ihre wichtigsten Unterschiede? Welche Patterns lassen sich mit dem aktuellen Pattern kombinieren?
Weiterführende Hintergrundinformationen, die zum besseren Verständnis der Design Patterns beitragen, finden Sie in den Anhängen dieses Buches: Anhang A hält ein Glossar mit der gängigen Designterminologie bereit. Im bereits erwähnten Anhang B werden die verschiedenen Notationen erläutert, deren Kernaspekte in den nachfolgenden Kapiteln beschrieben werden. Und im Anhang C finden Sie den Quelltext für die Basisklassen, die in den Codebeispielen verwendet werden.
Der in diesem Buch präsentierte Katalog umfasst insgesamt 23 Design Patterns, die im Folgenden in einer Kurzübersicht unter Angabe ihrer jeweiligen Namen sowie ihrer Zweckbestimmung vorgestellt werden. Eine ausführliche Beschreibung der einzelnen Patterns folgt ab Kapitel 3.
Hinweis
Hinter dem englischen Pattern-Namen ist stets auch die deutsche Entsprechung sowie das Kapitel bzw. der Abschnitt angegeben, in dem es detailliert besprochen wird. Diese Konvention wird im gesamten Buch durchgängig eingehalten.
Bereitstellung einer Schnittstelle zum Erzeugen verwandter oder voneinander abhängiger Objektfamilien ohne die Benennung ihrer konkreten Klassen.
Anpassung der Schnittstelle einer Klasse an ein anderes von den Clients erwartetes Interface. Das Design Pattern Adapter (Adapter) ermöglicht die Zusammenarbeit von Klassen, die ansonsten aufgrund der Inkompatibilität ihrer Schnittstellen nicht dazu in der Lage wären.
Entkopplung einer Abstraktion von ihrer Implementierung, so dass beide unabhängig voneinander variiert werden können.
Getrennte Handhabung der Erzeugungs- und Darstellungsmechanismen komplexer Objekte zwecks Generierung verschiedener Repräsentationen in einem einzigen Erzeugungsprozess.
Vermeidung der Kopplung eines Request-Auslösers mit seinem Empfänger, indem mehr als ein Objekt in die Lage versetzt wird, den Request zu bearbeiten. Die empfangenden Objekte werden miteinander verkettet und der Request wird dann so lange entlang dieser Kette weitergeleitet, bis er von einem Objekt angenommen und bearbeitet wird.
Kapselung eines Requests als Objekt, um so die Parametrisierung von Clients mit verschiedenen Requests, Warteschlangen- oder Logging-Operationen sowie das Rückgängigmachen von Operationen zu ermöglichen.
Komposition von Objekten in Baumstrukturen zur Abbildung von Teil-Ganzes-Hierarchien. Das Design Pattern Composite (Kompositum) gestattet den Clients einen einheitlichen Umgang sowohl mit individuellen Objekten als auch mit Objektkompositionen.
Dynamische Erweiterung der Funktionalität eines Objekts. Decorator-Objekte stellen hinsichtlich der Ergänzung einer Klasse um weitere Zuständigkeiten eine flexible Alternative zur Unterklassenbildung dar.
Bereitstellung einer einheitlichen Schnittstelle zu einem Schnittstellensatz in einem Subsystem. Das Design Pattern Facade (Fassade) definiert eine Schnittstelle höherer Ebene, die die Nutzung des Subsystems vereinfacht.
Definition einer Schnittstelle zur Objekterzeugung, wobei die Bestimmung der zu instanziierenden Klasse den Unterklassen überlassen bleibt. Das Design Pattern Factory Method (Fabrikmethode) gestattet einer Klasse, die Instanziierung an Unterklassen zu delegieren.
Gemeinsame Nutzung feingranularer Objekte, um sie auch in großer Anzahl effizient nutzen zu können.
Definition einer Repräsentation der Grammatik einer gegebenen Sprache sowie Bereitstellung eines Interpreters, der diese Grammatik nutzt, um in der betreffenden Sprache verfasste Sätze zu interpretieren.
Bereitstellung eines sequenziellen Zugriffs auf die Elemente eines aggregierten Objekts, ohne dessen zugrunde liegende Struktur offenzulegen.
Definition eines Objekts, das die Interaktionsweise eines Objektsatzes in sich kapselt. Das Design Pattern Mediator (Vermittler) begünstigt lose Kopplungen, indem es die explizite Referenzierung der Objekte untereinander unterbindet und so eine individuelle Steuerung ihrer Interaktionen ermöglicht.
Erfassung und Externalisierung des internen Zustands eines Objekts, ohne dessen Kapselung zu beeinträchtigen, so dass es später wieder in diesen Zustand zurückversetzt werden kann.
Definition einer 1-zu-n-Abhängigkeit zwischen Objekten, damit im Fall einer Zustandsänderung eines Objekts alle davon abhängigen Objekte entsprechend benachrichtigt und automatisch aktualisiert werden.
Spezifikation der zu erzeugenden Objekttypen mittels einer prototypischen Instanz und Erzeugung neuer Objekte durch Kopieren dieses Prototyps.
Bereitstellung eines vorgelagerten Stellvertreterobjekts bzw. eines Platzhalters zwecks Zugriffssteuerung eines Objekts.
Sicherstellung der Existenz nur einer einzigen Klasseninstanz sowie Bereitstellung eines globalen Zugriffspunkts für diese Instanz.
Anpassung der Verhaltensweise eines Objekts im Fall einer internen Zustandsänderung, so dass es den Anschein hat, als hätte es seine Klasse gewechselt.
Definition einer Familie von einzeln gekapselten, austauschbaren Algorithmen. Das Design Pattern Strategy (Strategie) ermöglicht eine variable und von den Clients unabhängige Nutzung des Algorithmus.
Definition der Grundstruktur eines Algorithmus in einer Operation sowie Delegation einiger Ablaufschritte an Unterklassen. Das Design Pattern Template Method (Schablonenmethode) ermöglicht den Unterklassen, bestimmte Schritte eines Algorithmus zu überschreiben, ohne dessen grundlegende Struktur zu verändern.
Darstellung einer auf die Elemente einer Objektstruktur anzuwendenden Operation. Das Design Pattern Visitor (Besucher) ermöglicht die Definition einer neuen Operation, ohne die Klassen der von ihr bearbeiteten Elemente zu verändern.
Design Patterns unterscheiden sich sowohl in ihrer Granularität als auch in ihrem Abstraktionsgrad. Und weil es eine ganze Reihe von Patterns gibt, müssen sie irgendwie organisiert werden.
Bei der im Folgenden dargestellten Klassifizierungsmethode werden die Design Patterns des hier vorgestellten Katalogs nach verwandtschaftlichen Beziehungsgraden gruppiert. Diese Art der Gliederung fördert nicht nur ein besseres und schnelleres Verständnis der einzelnen Pattern-Konzepte, sondern erleichtert auch das Auffinden neuer Patterns.
Zweck
Erzeugungsmuster(Creational Patterns)
Strukturmuster(Structural Patterns)
Verhaltensmuster(Behavioral Patterns)
Gültigkeitsbereich
Klasse
Factory Method (Fabrikmethode, Abschnitt 3.3)
Adapter (Adapter, (klassenbasiert), Abschnitt 4.1)
Interpreter (Interpreter, Abschnitt 5.3)
Template Method (Schablonenmethode, Abschnitt 5.10)
Objekt
Abstract Factory (Abstrakte Fabrik, Abschnitt 3.1)
Builder (Erbauer, Abschnitt 3.2)
Prototype (Prototyp, Abschnitt 3.4)
Singleton (Singleton, Abschnitt 3.5)
Adapter (Adapter, (objektbasiert), Abschnitt 4.1)
Bridge (Brücke, Abschnitt 4.2)
Composite (Kompositum, Abschnitt 4.3)
Decorator (Dekorierer, Abschnitt 4.4)
Facade (Fassade, Abschnitt 4.5)
Flyweight (Fliegengewicht, Abschnitt 4.6)
Proxy (Proxy, Abschnitt 4.7)
Chain of Responsibility (Zuständigkeitskette, Abschnitt 5.1)
Command (Befehl, Abschnitt 5.2)
Iterator (Iterator, Abschnitt 5.4)
Mediator (Vermittler, Abschnitt 5.5)
Memento (Memento, Abschnitt 5.6)
Observer (Beobachter, Abschnitt 5.7)
State (Zustand, Abschnitt 5.8)
Strategy (Strategie, Abschnitt 5.9)
Visitor (Besucher, Abschnitt 5.11)
Tabelle 1.1: Klassifizierung der Design Patterns
In diesem Buch werden die Design Patterns nach zwei übergeordneten Kriterien klassifiziert (siehe Tabelle 1.1). Das erste Kriterium gibt den Zweck des Patterns an, also was es bewirkt. Design Patterns dienen entweder der Objekterzeugung oder sie sind struktur- bzw. verhaltensorientiert:
Erzeugungsmuster beziehen sich auf den Erstellungsprozess von Objekten.
Strukturmuster wirken sich auf die Zusammensetzung von Klassen und Objekten aus.
Verhaltensmuster charakterisieren die Art und Weise der Interaktion von Klassen und Objekten sowie die Verteilung der Zuständigkeiten.
Das zweite Kriterium, der Gültigkeitsbereich, spezifiziert, ob das Design Pattern vorwiegend auf Klassen oder auf Objekte angewendet wird.
Klassenbasierte Design Patterns beeinflussen die Beziehungen der Klassen zu ihren Unterklassen, die durch Vererbung erstellt werden und damit statisch sind – d. h., sie werden schon beim Kompilieren festgelegt.
Objektbasierte Design Patterns haben dagegen Auswirkungen auf die Beziehungen zwischen Objekten, die sich zur Laufzeit ändern können und somit dynamischer sind.
Weil fast alle Design Patterns ein gewisses Maß an Vererbung nutzen, werden prinzipiell nur diejenigen als »klassenbasiert« bezeichnet, die sich auch wirklich auf die Klassenbeziehungen konzentrieren. Im Allgemeinen sind die meisten Design Patterns jedoch objektbasiert.
Klassenbasierte Erzeugungsmuster delegieren die Objekterstellung teilweise an Unterklassen, objektbasierte Erzeugungsmuster dagegen an ein anderes Objekt. Klassenbasierten Strukturmuster stützen sich bei der Zusammenführung auf das Konzept der Vererbung, objektbasierte Strukturmuster bilden dagegen Wege ab, um Objekte zusammenzufügen. Und klassenbasierte Verhaltensmuster greifen wiederum ebenfalls auf die Vererbung zurück, um sowohl Algorithmen als auch den Programmablauf zu bestimmen, während objektbasierte Verhaltensmuster beschreiben, in welcher Form eine Gruppe von Objekten interagiert, um einen Task auszuführen, der nicht von einem einzelnen Objekt bewerkstelligt werden kann.
Es gibt aber noch andere Möglichkeiten, die Design Patterns zu klassifizieren. Manche Patterns werden beispielsweise häufig gemeinsam verwendet, wie etwa Composite (Kompositum) mit Iterator (Iterator) oder Visitor (Besucher). Andere Patterns stellen hingegen Alternativen zueinander dar, wie z. B. im Fall von Prototype (Prototyp) und Abstract Factory (Abstrakte Fabrik), die oftmals alternativ eingesetzt werden. Und schließlich gibt es auch noch solche Patterns, die im Endeffekt zu gleichartigen Designs führen, obwohl sie jeweils unterschiedliche Zwecke erfüllen. Ein Beispiel hierfür sind Composite (Kompositum) und Decorator (Dekorierer), die sehr ähnliche Strukturdiagramme aufweisen.
Eine weitere Klassifizierungsvariante für Design Patterns basiert auf ihren verwandtschaftlichen Beziehungen zueinander, die in Abbildung 1.2 in einer Übersicht dargestellt sind.
Insgesamt gibt es eine Vielzahl von hilfreichen Klassifizierungsarten für Design Patterns – und sie alle haben durchaus ihre Daseinsberechtigung, denn: Unterschiedliche Denkansätze im Hinblick auf die Klassifizierung führen in jedem Fall zu einem weitreichenderen, besseren Verständnis der Funktionsweise der einzelnen Design Patterns, bieten aufschlussreiche Vergleichsmöglichkeiten und zeigen auf, wann ihr Einsatz sinnvoll ist.
Abb. 1.2: Verwandtschaftliche Beziehungen der Design Patterns
Design Patterns bieten objektorientierten Softwareentwicklern die Gelegenheit, zahlreichen Problemen, denen sie tagtäglich gegenüberstehen, auf sehr unterschiedliche Art und Weise zu begegnen. Die nachfolgenden Beispiele veranschaulichen einige solcher Problemfälle und demonstrieren, wie sie sich durch den Einsatz von Design Patterns lösen lassen.
Objektorientierte Programme setzen sich – wie der Name schon vermuten lässt – aus Objekten zusammen. Jedes Objekt umfasst neben dem reinen Datenbestand auch die prozeduralen Routinen – die sogenannten Methoden oder Operationen –, die auf diesen Daten basieren. Sobald ein Objekt einen Request (also eine Anfrage oder eine Nachricht) von einem Client erhält, führt es eine Operation aus.
Requests stellen die einzige Möglichkeit dar, ein Objekt zur Durchführung einer Operation zu veranlassen. Und Operationen bieten ihrerseits die einzige Möglichkeit, die internen Daten eines Objekts zu modifizieren. Dieses Konzept wird als Kapselung bezeichnet: Es ist kein direkter Zugriff auf den internen Zustand des Objekts möglich und er ist auch nicht nach außen hin sichtbar.
Eine besondere Herausforderung beim objektorientierten Design besteht in der Aufgliederung eines Systems in einzelne Objekte – denn dabei müssen zahlreiche Faktoren berücksichtigt werden, die sich nicht selten noch dazu in widersprüchlicher Art und Weise auswirken: die Kapselung, die Granularität, die Abhängigkeiten, die Flexibilität, das Laufzeitverhalten, die Evolution, die Wiederverwendbarkeit und vieles mehr.
Objektorientierte Designmethoden begünstigen in dieser Hinsicht viele verschiedene Ansätze. So könnte man beispielsweise die beherrschenden Substantive und Verben zur Beschreibung der vorliegenden Problemstellung herausgreifen und dazu passende Klassen und Operationen erstellen. Oder man richtet sein Augenmerk mehr auf die Interaktionen und Zuständigkeiten in dem System. Oder man fertigt ein realitätsnahes Modell an, analysiert es und überträgt die dabei ermittelten Objekte in das Design. Welcher Ansatz letztlich der beste ist, ist immer auch ein wenig Geschmackssache.
In vielen Fällen entstammen die Objekte dem realitätsnahen Analysemodell, darüber hinaus werden in objektorientierten Designs häufig aber auch Klassen benutzt, für die es keine realen Entsprechungen gibt. Einige davon, wie z. B. Arrays, besitzen einen niedrigen Abstraktionsgrad, andere hingegen weisen einen deutlich höheren auf. So nutzt beispielsweise das Design Pattern Composite (Kompositum, siehe Abschnitt 4.3) eine Abstraktion zur einheitlichen Behandlung von Objekten, für die es kein physisches Gegenstück gibt. Eine strikt an der realen Welt orientierte Modellbildung hat ein System zur Folge, das zwar die gegenwärtigen Realitäten widerspiegelt, nicht notwendigerweise aber auch zukünftige – der Schlüssel für eine flexible Gestaltung des Designs sind die Abstraktionen, die aus dem Designprozess hervorgehen.
Patterns erleichtern die Identifizierung der weniger deutlich erkennbaren Abstraktionen sowie der Objekte, die diese erfassen können. So finden sich für Objekte, die einen Prozess oder einen Algorithmus repräsentieren, normalerweise keine natürlichen Entsprechungen, trotzdem sind sie ein wichtiger Bestandteil flexibler Designs. Das Design Pattern Strategy (Strategie, siehe Abschnitt 5.9) beschreibt, wie sich austauschbare Algorithmusfamilien implementieren lassen. Und das Pattern State (Zustand, siehe Abschnitt 5.8) bildet jeden Zustand einer Entität in Form eines Objekts ab. Solche Objekte sind im Rahmen einer Analyse oder in einer frühen Phase des Designprozesses nur selten ausfindig zu machen, sondern treten meist erst später in Erscheinung, wenn es darum geht, das Design flexibler und wiederverwendbar auszugestalten.
Sowohl die Größe als auch die Anzahl der verwendeten Objekte kann erheblich variieren. Außerdem können sie von der Hardware bis zur vollständigen Anwendung so ziemlich alles repräsentieren. Wie also lässt sich bestimmen, was genau als Objekt genutzt werden sollte?
Auch auf diese Frage liefern Design Patterns eine Antwort. Das Pattern Facade (Fassade, siehe Abschnitt 4.5) beschreibt beispielsweise, wie sich komplette Subsysteme als Objekte abbilden lassen, und das Pattern Flyweight (Fliegengewicht, siehe Abschnitt 4.6) definiert die Unterstützung einer großen Zahl von Objekten geringster Granularität. Andere Design Patterns zeigen wiederum bestimmte Arten der Zergliederung eines Objekts in mehrere kleinere Objekte auf. Abstract Factory (Abstrakte Fabrik, siehe Abschnitt 3.1) und Builder (Erbauer, siehe Abschnitt 3.2) befassen sich mit Objekten, die ausschließlich für die Erzeugung anderer Objekte zuständig sind. Visitor (Besucher, siehe Abschnitt 5.11) und Command (Befehl, siehe Abschnitt 5.2) zielen dagegen auf Objekte ab, deren einzige Aufgabe in der Implementierung eines Requests an ein anderes Objekt bzw. eine Objektgruppe besteht.
Jede von einem Objekt deklarierte Operation weist eine sogenannte Signatur auf, die den Namen, die als Parameter enthaltenen Objekte sowie den Rückgabewert der Operation spezifiziert. Die Menge aller durch die Operationen eines Objekts definierten Signaturen wird als Schnittstelle des Objekts bezeichnet. Sie gibt vor, welche Requests an das Objekt übermittelt werden können – d. h. jeder Request, der einer in der Objektschnittstelle erfassten Signatur entspricht.
Der Typ eines Objekts bezeichnet eine bestimmte Schnittstelle. Wenn ein Objekt beispielsweise alle Requests für die in seiner Schnittstelle Window definierten Operationen akzeptiert, dann spricht man in diesem Fall davon, dass es vom Typ Window ist. Grundsätzlich kann ein Objekt mehrere Typen haben und umgekehrt können sehr verschiedene Objekte einen gemeinsamen Typ besitzen. Ebenso können einzelne Teile einer Objektschnittstelle unterschiedlichen Typs sein, d. h., zwei Objekte desselben Typs haben gegebenenfalls lediglich individuelle Schnittstellenbestandteile gemeinsam. Darüber hinaus können Schnittstellen auch andere Schnittstellen als Untermengen enthalten. Wenn die Schnittstelle eines Typs die Schnittstelle seines übergeordneten Supertyps enthält, wird dieser Typ als Subtyp bezeichnet. Häufig spricht man in diesem Zusammenhang auch davon, dass ein Subtyp die Schnittstelle seines Supertyps erbt.
Schnittstellen sind ein grundlegender Bestandteil objektorientierter Systeme. Die Objekte sind ausschließlich über ihre Schnittstellen bekannt. Grundsätzlich gibt es keine andere Möglichkeit, etwas über ein Objekt in Erfahrung zu bringen oder es zu etwas zu veranlassen, als über die Schnittstelle. Sie sagt jedoch nichts über die Implementierung des Objekts aus: Verschiedene Objekte können Requests auf ganz unterschiedliche Art und Weise implementieren – d. h., zwei Objekte mit völlig verschiedenen Implementierungen können dennoch identische Schnittstellen besitzen.
Bei der Übermittlung eines Requests an ein Objekt ist die daraufhin auszuführende Operation sowohl von dem Request als auch von dem empfangenden Objekt abhängig. Unterschiedliche Objekte, die identische Requests unterstützen, können wiederum verschiedene Implementierungen der Operationen nutzen, die diese Requests ausführen. Die zur Laufzeit erfolgende Zuweisung eines Requests zu einem Objekt und einer seiner Operationen wird als dynamische Bindung bezeichnet.
Dank der dynamischen Bindung ist beim Absetzen eines Requests bis zum Zeitpunkt der Ausführung keine Festlegung auf eine spezifische Implementierung erforderlich. Somit ist es problemlos möglich, Programme zu entwickeln, die Objekte mit bestimmten Schnittstellen erwarten, weil jedes Objekt, das die passende Schnittstelle aufweist, diesen Request akzeptieren wird. Zudem gestattet die dynamische Bindung auch die Substitution, sprich den Austausch von Objekten mit identischen Schnittstellen zur Laufzeit – dieses Schlüsselkonzept objektorientierter Systeme wird als Polymorphie bezeichnet. Dadurch braucht ein Client-Objekt außer in Bezug auf deren Unterstützung einer bestimmten Schnittstelle keine weiteren Annahmen über andere Objekte zu treffen. Die Polymorphie vereinfacht also die Client-Definition, entkoppelt Objekte voneinander und gestattet es ihnen außerdem, ihre wechselseitigen Beziehungen zur Laufzeit zu variieren.
Design Patterns erleichtern die Schnittstellendefinition, indem sie ihre Schlüsselelemente sowie die Datentypen, die über eine Schnittstelle geleitet werden, identifizieren. Mitunter geben sie auch vor, was nicht in einer Schnittstelle enthalten sein sollte. So beschreibt zum Beispiel das Pattern Memento (Memento, siehe Abschnitt 5.6), wie der interne Zustand eines Objekts gekapselt und gespeichert sein muss, damit das Objekt zu einem späteren Zeitpunkt wieder in diesen Zustand zurückversetzt werden kann. Hier lautet die Vorgabe, dass Memento-Objekte zwei Schnittstellen definieren müssen: eine eingeschränkte Schnittstelle, die den Clients zum Verwahren und Kopieren der Mementos dient, und eine privilegierte Schnittstelle, die ausschließlich vom ursprünglichen Objekt genutzt werden kann, um seinen Zustand im Memento zu speichern und wiederherzustellen.
Darüber hinaus spezifizieren Design Patterns auch die Beziehungen zwischen den Schnittstellen. Insbesondere setzen sie häufig voraus, dass einige Klassen ähnliche Schnittstellen aufweisen oder sehen Einschränkungen für bestimmte Klassenschnittstellen vor. So erfordern sowohl Decorator (Dekorierer, siehe Abschnitt 4.4) als auch Proxy (Proxy, siehe Abschnitt 4.7), dass die Schnittstellen der Decorator- und Proxy-Objekte mit denen der dekorierten bzw. stellvertretenen Objekten identisch sind. Und beim Pattern Visitor (Besucher, siehe Abschnitt 5.11) muss die Visitor-Schnittstelle die Klassen aller Objekte abbilden, die der Visitor besuchen kann.
Nachdem sie bislang lediglich am Rande erwähnt wurde, soll die konkrete Definition eines Objekts an dieser Stelle einmal ausführlicher betrachtet werden. Die Implementierung eines Objekts wird durch seine Klasse definiert. Sie spezifiziert die internen Daten sowie die Repräsentation des Objekts und bestimmt darüber hinaus auch, welche Operationen es ausführen kann.
In der auf der Object-Modeling Technique (OMT, Objekt-Modellierungstechnik) basierenden Notation (siehe Anhang B) wird eine Klasse in einem Objektdiagramm wie folgt dargestellt: Zuoberst ist der Klassenname in Fettschrift angegeben, das nächste Segment enthält eine Auflistung der Operationen in Normalschrift, und zum Schluss sind die von der Klasse definierten Daten aufgeführt (siehe Abbildung 1.3).
Abb. 1.3: Objektdiagramm einer Klasse
Da hier nicht von einer statisch typisierten Implementierungssprache ausgegangen wird, sind die Typen der Rückgabewerte und Instanzvariablen optional.
Objekte werden durch die Instanziierung einer Klasse erzeugt und dementsprechend als Instanzen der Klasse bezeichnet. Während des Instanziierungsvorgangs wird zum einen der Speicherbereich für die internen (aus den Instanzvariablen bestehenden) Daten des Objekts angelegt, und zum anderen werden die Operationen mit diesen Daten verknüpft. Durch die Instanziierung einer Klasse können viele ähnliche Instanzen eines Objekts erzeugt werden.
Eine Klasse, die Objekte einer anderen Klasse instanziiert, wird durch einen gestrichelten Pfeil gekennzeichnet. Die Pfeilspitze verweist dabei auf die Klasse der instanziierten Objekte:
Abb. 1.4: Instanziierung von Objekten anderer Klassen
Die Klassenvererbung ermöglicht die Erstellung neuer Klassen, die auf bereits existierenden Klassen basieren. Und wenn eine Unterklasse (auch abgeleitete oder Subklasse genannt) von einer Basisklasse (auch als Eltern- oder Superklasse bezeichnet) erbt, beinhaltet sie auch die Definitionen aller zugehörigen Daten und Operationen, die in der Basisklasse definiert sind. Somit enthalten die Objekte, die Instanzen der Unterklasse sind, ebenfalls alle in der Unterklasse und ihren Basisklassen definierten Daten und sind in der Lage, sämtliche dort definierten Operationen auszuführen. Die Beziehung von Unterklasse und Basisklasse ist in Abbildung 1.5 durch eine vertikale Linie mit aufgesetztem Dreiecksymbol gekennzeichnet:
Abb. 1.5: Beziehung Basisklasse – Unterklasse
Der Hauptzweck einer abstrakten Klasse besteht in der Definition einer gemeinsamen Schnittstelle für alle ihre Unterklassen. Da solche Klassen ihre Implementierungen teilweise oder auch vollumfänglich an die in den Unterklassen definierten Operationen delegieren, können sie nicht instanziiert werden. Die von einer abstrakten Klasse deklarierten, aber nicht implementierten Operationen werden als abstrakte Operationen bezeichnet. Nicht abstrakte Klassen werden konkrete Klassen genannt.
Unterklassen können das Verhalten ihrer Basisklassen weiter verfeinern und auch umdefinieren. Genauer gesagt: Eine Klasse ist in der Lage, eine von ihrer Basisklasse definierte Operation zu überschreiben. Dadurch können Unterklassen stellvertretend für ihre Basisklassen die Bearbeitung von Requests übernehmen. Das Konzept der Klassenvererbung gestattet also nicht nur die Definition neuer Klassen durch die schlichte Erweiterung anderer Klassen, sondern erleichtert auch die Definition von Objektfamilien mit verwandter Funktionalität.
Zur besseren Unterscheidung von den konkreten Klassen sind die Namen von abstrakten Klassen – ebenso wie die abstrakten Operationen – in den nachfolgenden Diagrammen in Kursivschrift angegeben. Außerdem können die Diagramme auch Pseudocode für die Implementierung einer Operation ausweisen, der dann durch ein Eselsohr gekennzeichnet und über eine gestrichelte Linie mit der implementierten Operation verbunden ist (siehe Abbildung 1.6):
Abb. 1.6: Abstrakte und konkrete Klassen
Abb. 1.7: Mehrfachvererbung der Mixin-Klasse
Eine Mixin-Klasse ist eine Klasse, die eine optionale Schnittstelle oder Funktionalität für andere Klassen bereitstellt. Mit einer abstrakten Klasse hat sie gemein, dass sie ebenfalls nicht instanziiert wird. Mixin-Klassen bedingen eine Mehrfachvererbung.
Zwischen der Klasse und dem Typ eines Objekts besteht ein wesentlicher Unterschied: Die Klasse definiert, wie das Objekt implementiert ist. Sie beschreibt seinen internen Zustand und die Implementierung seiner Operationen. Im Gegensatz dazu bezieht sich der Typ eines Objekts ausschließlich auf seine Schnittstelle – die Gesamtheit der Requests, auf die es antworten kann. Ein Objekt kann viele Typen haben, andererseits können Objekte verschiedener Klassen aber auch denselben Typ besitzen.
Selbstverständlich stehen Klasse und Typ in einer engen Beziehung zueinander: Weil eine Klasse die Operationen definiert, die ein Objekt ausführen kann, bestimmt sie somit auch den Typ des Objekts. Die Feststellung, dass ein Objekt eine Instanz einer Klasse ist, bedeutet also im Grunde genommen, dass das Objekt die von der Klasse vorgegebene Schnittstelle unterstützt.
In Programmiersprachen wie C++ und Eiffel werden Klassen zur Spezifikation sowohl des Typs als auch der Implementierungen eines Objekts verwendet. Smalltalk-Programme deklarieren hingegen keine Typen für Variablen, und dementsprechend überprüft der Compiler auch nicht, ob die einer Variablen zugewiesenen Objekttypen Subtypen des Variablentyps sind. Das Versenden eines Requests erfordert zwar eine dahingehende Überprüfung, dass die Klasse des Empfängers den Request auch implementiert – ob der Empfänger eine Instanz einer bestimmten Klasse ist, muss jedoch nicht geprüft werden.
Ein weiterer wichtiger Aspekt, den es in diesem Zusammenhang zu beachten gilt, ist der Unterschied zwischen der Klassenvererbung und der Schnittstellenvererbung (auch als Subtyping bezeichnet). Bei der Klassenvererbung wird die Implementierung eines Objekts in Form der Implementierung eines anderen Objekts definiert. Es handelt sich also um einen Mechanismus zur Wiederverwertung bzw. Wiederverwendung von Code und Repräsentation. Bei der Schnittstellenvererbung geht es im Gegensatz dazu darum, wann ein Objekt anstelle eines anderen Objekts verwendet werden kann.
