Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort
1 Einführung
2 Mathematische und technische Grundlagen
3 Hardware
4 Netzwerkgrundlagen
5 Betriebssystemgrundlagen
6 Windows
7 Linux
8 Mac OS X
9 Grundlagen der Programmierung
10 Konzepte der Programmierung
11 Software-Engineering
12 Datenbanken
13 Server für Webanwendungen
14 Weitere Internet-Serverdienste
15 XML
16 Weitere Datei- und Datenformate
17 Webseitenerstellung mit (X)HTML und CSS
18 Webserveranwendungen
19 JavaScript und Ajax
20 Computer- und Netzwerksicherheit
A Glossar
B Zweisprachige Wortliste
C Kommentiertes Literatur- und Linkverzeichnis
Stichwort

Jetzt Buch bestellen
Ihre Meinung?

Spacer
IT-Handbuch für Fachinformatiker von Sascha Kersken
Der Ausbildungsbegleiter
Buch: IT-Handbuch für Fachinformatiker

IT-Handbuch für Fachinformatiker
Rheinwerk Computing
1216 S., 6., aktualisierte und erweiterte Auflage, geb.
34,90 Euro, ISBN 978-3-8362-2234-1
Pfeil 11 Software-Engineering
Pfeil 11.1 Überblick
Pfeil 11.1.1 Der Entwicklungszyklus
Pfeil 11.1.2 Planung und Analyse
Pfeil 11.1.3 Entwurf
Pfeil 11.1.4 Implementierung und Test
Pfeil 11.1.5 Dokumentation
Pfeil 11.1.6 Konkrete Entwicklungsverfahren
Pfeil 11.2 Werkzeuge
Pfeil 11.2.1 UML
Pfeil 11.2.2 Entwurfsmuster
Pfeil 11.2.3 Unit-Tests
Pfeil 11.3 Zusammenfassung

Rheinwerk Computing - Zum Seitenanfang

11.2 WerkzeugeZur nächsten Überschrift

In diesem Abschnitt werden einige wichtige Hilfsmittel für die Softwareentwicklung vorgestellt:

  • Die Diagrammspezifikation UML (Unified Modeling Language) bietet verschiedene standardisierte Diagrammtypen zur Darstellung diverser Projektbestandteile in den verschiedenen Phasen des Entwicklungsprozesses.
  • Entwurfsmuster katalogisieren erfolgreiche Lösungsmodelle für die einfache Übernahme in späteren Projekten.
  • Unit-Tests ermöglichen den automatisierten Test der Funktionalität einer Klasse.

Diese Hilfsmittel können in vielen unterschiedlichen Entwicklungsmodellen zum Einsatz kommen, obwohl einige von ihnen im Zusammenhang mit konkreten Verfahren entwickelt wurden.


Rheinwerk Computing - Zum Seitenanfang

11.2.1 UMLZur nächsten ÜberschriftZur vorigen Überschrift

Der Begriff Sprache für die Unified Modeling Language (UML) ist ein wenig irreführend: Zwar handelt es sich um eine bestimmte Ausdrucksweise, aber ihr Vokabular sind keine Wörter, sondern verschiedene Arten von Diagrammen. Die UML ist gut dazu geeignet, die diversen Aspekte der Softwareentwicklung übersichtlich darzustellen. Ihr Arbeitsschwerpunkt sind die Analyse- und die Entwurfsphase.

Die UML wurde in den 90er-Jahren von den »drei Amigos« Grady Booch, Ivar Jacobson und James Rumbaugh durch Zusammenführung einiger früherer Ansätze entwickelt. Zunächst wurde sie vor allem von dem Entwicklungstool-Hersteller Rational gefördert und weiterentwickelt. Später wurde sie von der Object Management Group (OMG) standardisiert und fand allgemeine Verbreitung. Ende 2003 wurde die neue Version 2.0 veröffentlicht, die einige Erweiterungen und Verbesserungen bietet.

Die wichtigsten Diagrammtypen der UML sind die Folgenden:

  • Anwendungsfalldiagramme (Use Case Diagrams) stellen die Anforderungen der Benutzer dar. Sie prägen das Bild der UML in der Öffentlichkeit, weil sie die typischen »Strichmännchen« (Akteure) definieren.
  • Klassendiagramme (Class Diagrams) verdeutlichen die Klassenstruktur und damit die Grundarchitektur des Systems.
  • In Kollaborationsdiagrammen (Collaboration Diagrams) wird das Zusammenwirken verschiedener Objekte dargestellt.
  • Sequenzdiagramme (Sequence Diagrams) stellen besonders den zeitlichen Ablauf dieser Zusammenarbeit dar.
  • In Zustandsdiagrammen (State Diagrams) werden die verschiedenen Zustände eines Objekts und die Übergänge zwischen diesen Zuständen dargestellt.
  • Pakete (Packages) dienen der Verdeutlichung hierarchischer Objektstrukturen.
  • Aktivitätsdiagramme (Activity Diagrams) stellen den logischen und zeitlichen Ablauf verschiedener Aktivitäten dar.

Es ist nicht Sinn der Sache, UML-Diagramme auf Dauer mit dem Stift auf Papier zu malen (außer für erste Ideensammlungen oder bei Projektbesprechungen). Sie sollten die Diagramme auch nicht mühevoll in einem allgemeinen Grafikprogramm wie Illustrator zeichnen. Viel verbreiteter und erheblich produktiver sind UML-Tools, mit denen sich die Diagramme einfach, schnell und übersichtlich erstellen lassen. Solche Programme werden auch als CASE-Tools (Computer-Aided Software Engineering) bezeichnet. Es gibt sowohl Open-Source-Lösungen als auch sehr teure kommerzielle Produkte.

Ein praktisches Open-Source-Tool ist ArgoUML (http://argouml.tigris.org). Es ist in Java geschrieben und läuft somit auf den meisten wichtigen Systemen. Eine Installation ist nicht erforderlich. Sie benötigen lediglich eine funktionierende Java-Installation (siehe Kapitel 9, »Grundlagen der Programmierung«). Laden Sie die Archivdatei herunter, entpacken Sie sie in ein beliebiges neues Verzeichnis, und geben Sie Folgendes ein, um das Programm zu starten:

$ java -jar argouml.jar

Alternativ lässt sich das Tool in neueren Browsern auch durch einen Java Web Start-Link auf der Website direkt starten.

Abbildung 11.3 zeigt das Programm beim Erstellen eines Klassendiagramms. Die obere der beiden Symbolleisten enthält Schaltflächen für die verschiedenen Diagrammtypen, die untere bietet Werkzeuge für die einzelnen Bestandteile des aktuellen Diagramms. Nachdem Sie einen dieser Buttons angeklickt haben, können Sie das Element mit einem weiteren Klick auf der Arbeitsfläche platzieren und im unteren Feld seine Eigenschaften einstellen. Aus dem jeweils ausgewählten Element lassen sich automatisch Verbindungslinien herausziehen, um es mit anderen Elementen zu verknüpfen.

Im Folgenden werden vier UML-Diagrammarten näher vorgestellt: Anwendungsfalldiagramme, Klassendiagramme, Sequenzdiagramme und Aktivitätsdiagramme.

Abbildung

Abbildung 11.3 ArgoUML beim Erstellen eines Klassendiagramms

Anwendungsfalldiagramme

In einem Anwendungsfalldiagramm werden Anwendungsfälle (Use Cases) aus der Sicht der Beteiligten, der sogenannten Akteure, dargestellt. Dieser Diagrammtyp ist vornehmlich ein Bestandteil der Analyse. Abbildung 11.4 zeigt ein Beispiel mit zwei Akteuren, einem Kunden und einem Verkäufer. Die drei möglichen Geschäftsvorfälle, die zwischen ihnen stattfinden können, sind Information, Verhandlung und Kauf.

Die <<include>>-Beziehung zwischen Kauf und Verhandlung bedeutet, dass der Verkauf eine Verhandlung umfassen kann; ebenso verhält es sich mit der Beziehung zwischen Verhandlung und Information. Andere mögliche Beziehungen sind:

  • Eine durchgehende Linie mit einer hohlen Pfeilspitze steht für eine Generalisierung; der Anwendungsfall am Pfeilanfang ist eine Spezialisierung desjenigen am Pfeilende.
  • Ein gestrichelter Pfeil mit der Beschriftung <<extend>> besagt, dass der Anwendungsfall am Pfeilursprung denjenigen, auf den gezeigt wird, erweitert.

    Abbildung

    Abbildung 11.4 Ein einfaches UML-Anwendungsfalldiagramm

Bei der späteren Implementierung können sich aus diesen Beziehungen Vererbungsverhältnisse ergeben.

Klassendiagramme

Klassendiagramme stellen die Elemente einer Klasse sowie die Beziehungen zwischen verschiedenen Klassen dar. Jede Klasse wird durch ein Rechteck dargestellt; es gibt drei Detailstufen:

  • nur Klassenname
  • Klassenname und Attribute (Eigenschaften)
  • Klassenname, Attribute und Methoden

Die (bis zu) drei Kategorien werden durch waagerechte Linien voneinander getrennt. Abstrakte Klassen (ohne Methodenimplementierung) werden durch <<abstrakt>> unter dem Klassennamen gekennzeichnet, Interfaces durch <<interface>>.

Die Vererbung wird durch einen Pfeil mit leerer Spitze dargestellt, der von der abgeleiteten auf die übergeordnete Klasse zeigt. In Abbildung 11.5 sehen Sie die Beziehungen zwischen der Oberklasse Artikel und den abgeleiteten Klassen Buch und DVD, jeweils mit allen drei Informationen. Die zusätzliche Angabe der Datentypen von Methoden und Attributen ermöglicht die automatische Erzeugung des Code-Grundgerüsts für die Klassen. In ArgoUML erfolgt dies beispielsweise über Generieren · Alle Klassen generieren, sofern gerade ein Klassendiagramm angezeigt wird.

Abbildung

Abbildung 11.5 Ein UML-Klassendiagramm mit einer Elternklasse und zwei abgeleiteten Klassen

Neben den Vererbungslinien gibt es auch komplexere Beziehungen zwischen Klassen beziehungsweise Instanzen. Allgemein spricht man bei der Vererbung von »IS A«-Beziehungen, denn eine speziellere Klasse »ist« auch immer die Elternklasse. Im vorliegenden Beispiel gilt etwa, dass ein Buch unter anderem ein Artikel ist. Enthält eine Klasse Attribute, die Instanzen einer anderen Klasse sind, spricht man dagegen von einer »HAS A«-Beziehung – ein Objekt »hat« oder enthält ein anderes.

In der UML werden »HAS A«-Beziehungen durch eine Verbindungslinie zwischen den beiden Klassen dargestellt. Auf der Seite der Klasse, die die andere enthält, wird die Linie durch eine Raute gekennzeichnet. Hier gibt es zwei Möglichkeiten:

  • Aggregation: Die enthaltenen Elemente existieren unabhängig von der enthaltenden Klasse; dies wird durch eine leere Raute gekennzeichnet.
  • Komposition: Die enthaltenen Elemente existieren nur in Abhängigkeit von der enthaltenden Klasse, sind also eher abstrakte Eigenschaften als konkrete Gegenstände; diese Beziehung wird durch eine gefüllte Raute dargestellt.

Die Kardinalität (Häufigkeit) der beteiligten Elemente kann durch Zahlen an den Verbindungsstellen dargestellt werden:

  • Eine einfache Zahl (zum Beispiel 1) bedeutet, dass genauso viele Elemente dieses Typs beteiligt sind. Beispielsweise könnte man die Beziehung zwischen einem Pkw und seinen Rädern durch die festen Zahlen 1 beziehungsweise 4 kennzeichnen.
  • Ein Sternchen (*) steht für beliebig viele, also kein Element, ein Element oder mehrere Elemente.
  • Ein Zahlenbereich (m..n) bedeutet mindestens m und höchstens n Elemente. Die Beziehung zwischen einem allgemeinen Fahrzeug und seinen Rädern ist beispielsweise 1 zu 1..* (von der Schubkarre bis zum beliebig langen Güterzug).

Neben den »HAS A«-Beziehungen gibt es auch diverse lockere Verbindungen zwischen Klassen, genannt Assoziationen. Sie werden durch eine Verbindungslinie dargestellt. Eine offene Pfeilspitze oder ein daneben gezeichnetes Richtungsdreieck sowie eine Textbezeichnung beschreiben die Beziehung näher. Auch hier können die Kardinalitätsbezeichnungen verwendet werden.

In Abbildung 11.6 finden Sie zwei Beispiele für solche Klassenbeziehungen: Ein Supermarkt kann eine oder mehrere Kassen enthalten; da diese unabhängig vom Supermarkt existieren, bleibt die Raute leer (Aggregation). Die zweite Beziehung zeigt, dass ein Kassierer beliebig viele Artikel registrieren kann. Die Kardinalität 1 bei Supermarkt beziehungsweise Kassierer wurde als »Standardfall« einfach weggelassen; dies ist eine (zulässige) Eigenart von ArgoUML.

Abbildung

Abbildung 11.6 Klassenbeziehungen in UML-Klassendiagrammen: Aggregation (oben) und Assoziation (unten)

Sequenzdiagramme

In einem Sequenzdiagramm wird der zeitliche Ablauf einer Anwendung als Abfolge von Nachrichten zwischen den Objekten dargestellt. Die beteiligten Objekte werden waagerecht auf »Swim Lanes« (Schwimmbahnen) nebeneinander platziert. Der Arbeitsablauf wird von oben nach unten dargestellt. Auf jeder Bahn wird eine gestrichelte Linie gezeichnet, solange ein Objekt existiert, oder ein schmales Rechteck, wenn das Objekt gerade den Programmfluss kontrolliert. Methodenaufrufe werden mithilfe von durchgezogenen Pfeilen gekennzeichnet, Nachrichten durch gestrichelte Pfeile. Abbildung 11.7 zeigt das einfache Beispiel eines Einkaufsvorgangs.

Abbildung

Abbildung 11.7 UML-Sequenzdiagramm eines Einkaufsvorgangs

Aktivitätsdiagramme

Aktivitätsdiagramme lassen sich als Weiterentwicklung der klassischen Flussdiagramme betrachten. Sie dienen der Darstellung des Zusammenspiels verschiedener Aktivitäten, die durch seitlich abgerundete Kästen dargestellt werden. Andere wichtige Symbole sind diese:

  • Ein dicker, gefüllter Kreis markiert den Startzustand.
  • Ein hohler Kreis, der einen kleineren, gefüllten enthält, kennzeichnet den Endzustand, der alle Aktivitäten abschließt.
  • Fallentscheidungen werden durch eine Raute mit mehreren abgehenden Pfeilen dargestellt.
  • Eine dicke horizontale Linie dient entweder der Aufteilung (Forking) eines Ablaufs in mehrere parallele Verarbeitungsstränge oder der Zusammenführung zuvor verzweigter Abläufe.

In Abbildung 11.8 werden mögliche Abläufe des Anwendungsfalls »Information« aus dem Anwendungsfalldiagramm in Abbildung 11.3 verdeutlicht. In ArgoUML können Sie ein Aktivitätsdiagramm eines Anwendungsfalls erstellen, sobald dieser markiert ist.

Abbildung

Abbildung 11.8 UML-Aktivitätsdiagramm des Anwendungsfalls »Information«


Rheinwerk Computing - Zum Seitenanfang

11.2.2 EntwurfsmusterZur nächsten ÜberschriftZur vorigen Überschrift

Entwurfsmuster (Design Patterns) sind ursprünglich ein Konzept aus der (Gebäude-) Architektur, das im Rahmen der Programmiersprache und -umgebung Smalltalk für die Softwareentwicklung übernommen wurde. Im Wesentlichen geht es um die übersichtliche Katalogisierung einmal gefundener Lösungen für die spätere Wiederverwendung. Beachten Sie, dass Entwurfsmuster keine fertig programmierten Komponenten oder Codeschnipsel sind. Wie der Name schon sagt, gehören sie zur Phase des Entwurfs und nicht zur Implementierung von Software. Dennoch enthält ein Muster neben vielen anderen Komponenten auch Codebeispiele.

In der Softwareentwicklung wurden die Entwurfsmuster durch die »Gang of Four« (GoF) Erich Gamma (der ehemalige Eclipse-Entwicklungsleiter), Richard Helm, Ralph Johnson und John Vlissides eingeführt. Ihr Buch »Design Patterns« (siehe Anhang C) ist die wichtigste Informationsquelle und gewissermaßen der ursprüngliche Hauptkatalog für Entwurfsmuster. Daneben wurden zahlreiche weitere Musterkataloge entwickelt, beispielsweise sogenannte Enterprise Design Patterns, die auch Geschäftsvorfälle einbeziehen.

Ein bekanntes Entwurfsmuster, das nicht im GoF-Katalog vorkommt, ist zum Beispiel das MVC-Pattern (Model, View, Controller). Es handelt sich um eine praktische Vorgehensweise zur sauberen Trennung von Datenmodell, Programmlogik und Präsentation. Es wurde bereits in den 70er-Jahren im Smalltalk-Umfeld entwickelt und beschreibt den Idealzustand von APIs für grafische Benutzeroberflächen. Inzwischen wird es aber auch für Web-Frameworks wie Ruby on Rails (siehe Kapitel 18, »Webserveranwendungen«) genutzt.

Schema für Entwurfsmuster

Jedes Entwurfsmuster besteht aus vier wesentlichen Komponenten:

  • Name: Das Muster sollte eine möglichst griffige Bezeichnung erhalten, die möglichst genau auf seinen Verwendungszweck hindeutet.
  • Problem: eine genaue Beschreibung der Situation, in der das Entwurfsmuster eingesetzt werden kann
  • Lösung: die abstrakte Beschreibung eines Entwurfs, der das Problem löst
  • Konsequenzen: eine Beschreibung der Folgen und möglichen Nebeneffekte, die der Einsatz des Patterns mit sich bringt

Pattern-Kataloge wie derjenige in dem zuvor genannten Buch »Design Patterns« verwenden allerdings eine erheblich genauere Struktur zur Beschreibung jedes Musters. Es handelt sich um eine Auflistung der folgenden Punkte (in Klammern jeweils die Originalbezeichnungen aus dem GoF-Buch):

  • Name und Einordnung (Pattern Name and Classification): Die Bedeutung eines sprechenden Namens braucht unter Programmierern nicht weiter betont zu werden. Die Einordnung beschreibt das Einsatzgebiet (Purpose) und den Geltungsbereich (Scope) des Musters. Man unterscheidet drei grundlegende Einsatzgebiete: Erzeugungsmuster (Creational Patterns) sind Lösungen für verschiedene Probleme der Objekterzeugung; Strukturmuster (Structural Patterns) beschäftigen sich mit Problemstellungen der Datenstruktur, und Verhaltensmuster (Behavioral Patterns) beschreiben die Implementierung häufig benötigter Verhaltensweisen von Objekten. Der Geltungsbereich ist »Klasse« für statische, durch Vererbung angewendete Muster oder »Objekt« für Muster, die Objektbeziehungen betreffen. Letztere kommen wesentlich häufiger vor.
  • Absicht (Intent); kurze Beschreibung der Aufgabe des Entwurfsmusters und mögliche Gründe für seinen Einsatz
  • Alias (Also Known As): Viele Muster sind unter verschiedenen Namen bekannt; andere gängige Bezeichnungen werden hier aufgelistet.
  • Motivation: ein konkretes Beispielszenario, das den Einsatzzweck des Musters deutlich macht
  • Verwendungszweck (Applicability): Beschreibung der Situationen, in denen das Pattern eingesetzt werden kann, und der Probleme, die es lösen hilft
  • Struktur (Structure): grafische Darstellung der Klassen des Entwurfsmusters, meist UML-basiert
  • Beteiligte (Participants): Klassen und Objekte, die in die Anwendung des Musters involviert sind
  • Zusammenspiel (Collaborations): Beschreibung der Zusammenarbeit zwischen den Beteiligten
  • Konsequenzen (Consequences): Ergebnisse sowie Vor- und Nachteile der Anwendung des Musters
  • Implementierung (Implementation): Beschreibung von Besonderheiten und möglichen Problemen bei einer Implementierung des Musters
  • Codebeispiele (Sample Code): Das GoF-Buch verwendet C++ und/oder Smalltalk; in neueren Büchern und Websites wird meist Java oder C# benutzt. Prinzipiell kann jede objektorientierte Programmiersprache zum Einsatz kommen.
  • Einsatzbeispiele (Known Uses): Beispiele für die Anwendung dieser Muster in realen Softwaresystemen
  • Querverweise (Related Patterns): Zusammenarbeit dieses Entwurfsmusters mit anderen Mustern; gegebenenfalls Gemeinsamkeiten und Unterschiede

Der Originalkatalog aus dem Gang-of-Four-Buch

Das Buch »Design Patterns« enthält einen Katalog von 23 Mustern, die nach diesem Schema aufgelistet werden. Es handelt sich um Probleme, vor denen eines Tages jeder steht, der größere objektorientierte Programme schreiben möchte. Es hält Sie allerdings nichts davon ab, Ihre eigenen gelungenen Lösungsansätze ebenfalls nach diesem Schema zu katalogisieren. Wenn Sie einem Entwicklungsteam angehören, könnten Ihre Kollegen Ihnen eines Tages dafür dankbar sein.

In sehen Sie eine Kurzübersicht über die 23 GoF-Muster. Die Reihenfolge hält sich an diejenige im Buch: Die Muster werden innerhalb jeder der drei Purpose-Bereiche alphabetisch sortiert. Die Inhalte der Spalten »Name (Aliasse)« und »Einordnung« entsprechen der Beschreibung aus der zuvor dargestellten Aufzählung; die Spalte »Beschreibung« schildert kurz und knapp, was das jeweilige Muster leistet, enthält also die Informationen aus dem Katalogabschnitt »Absicht« und gegebenenfalls ein paar zusätzliche Hinweise. Im nachfolgenden Abschnitt finden Sie ein vollständig ausgeführtes Beispiel für ein GoF-Entwurfsmuster.

In den Pattern-Beschreibungen taucht des Öfteren der Begriff Client auf. Dabei handelt es sich in aller Regel nicht um einen Netzwerkclient, sondern um diejenige Klasse oder das Objekt, das sich der Dienstleistung des jeweiligen Patterns bedient.

Tabelle 11.2 Der vollständige Katalog der Entwurfsmuster aus dem Gang-of-Four-Buch »Design Patterns«

Name (Aliasse) Einordnung Beschreibung

Abstract Factory
(Kit)

Entwurf
Objekt

Interface zur Erzeugung verwandter Objekte, ohne ihre konkrete Klasse angeben zu müssen

Builder

Entwurf
Objekt

Hilfsmittel zur Erzeugung komplexer Strukturen aus verschiedenartigen Einzelobjekten durch Trennung von Erzeugung und Objektspeicherung

Factory Method
(Virtual Contructor)

Entwurf
Klasse

Interface zur Erzeugung von Objekten, bei dem die abgeleiteten Klassen im Gegensatz zur Abstract Factory über die zu instanziierende Klasse entscheiden können

Prototype

Entwurf
Objekt

vorgefertigte Instanz einer Klasse mit bestimmten Wunscheigenschaften, aus der durch Klonen weitere Objekte erzeugt werden

Singleton

Erzeugung
Objekt

Sicherstellen, dass eine Klasse nur genau eine einzige Instanz hat, und den globalen Zugriff auf diese ermöglichen

Adapter
(Wrapper)

Struktur
Objekt, Klasse

Das Interface einer Klasse in ein anderes Interface umwandeln, damit inkompatible Klassen zusammenarbeiten können. Klassenadapter verwenden die (nicht in allen Sprachen verfügbare) Mehrfachvererbung, Objektadapter benutzen Komposition.

Bridge
(Handle, Body)

Struktur
Objekt

eine Abstraktion und ihre Implementierung voneinander trennen, damit jede von ihnen separat modifiziert oder ausgetauscht werden kann

Composite

Struktur
Objekt

ein gemeinsames Interface für alle Einzelklassen einer Objekthierarchie sowie für Sammlungen dieser Klassen bereitstellen, damit der Zugriff auf beides in identischer Weise erfolgen kann

Decorator
(Wrapper)

Struktur
Objekt

Zusätzliche Eigenschaften zu einem Objekt hinzufügen, indem ein Decorator-Objekt für diese Eigenschaft erzeugt wird, das die ursprüngliche Instanz als Eigenschaft enthält – ermöglicht dynamische Erweiterungen der Funktionalität ohne Vererbung.

Facade

Struktur
Objekt

ein verallgemeinertes Interface für den vereinfachten Zugriff auf die spezielleren Interfaces von Subsystemen bereitstellen, wenn deren Detailtiefe bei den meisten Zugriffen nicht benötigt wird

Flyweight

Struktur
Objekt

Sehr viele Instanzen einer bestimmten Klasse mit wenig objektspezifischem Verhalten werden aus Performancegründen durch eine einzige virtuelle Instanz und eine Verwaltungsinstanz für die Daten der bisherigen Einzelinstanzen ersetzt.

Proxy
(Surrogate)

Struktur
Objekt

ein Platzhalter für ein anderes Objekt (das sich beispielsweise auf einem Remote-Rechner oder Datenträger befindet), der stellvertretend den Zugriff auf das eigentliche Objekt kontrolliert

Chain of Responsibility

Verhalten
Objekt

Anfragen nicht an das eigentliche Empfängerobjekt senden, sondern von diesem abkoppeln und Handler bereitstellen, die einen Zugriff durch beliebig viele Verarbeitungsschritte ermöglichen

Command
(Action, Transaction)

Verhalten
Objekt

Anfragen als Objekte kapseln, um sie von verschiedenen Stellen aus verarbeiten oder zur späteren Verarbeitung speichern zu können (bekanntes Beispiel: Menü- oder Button-Befehle in GUIs)

Interpreter

Verhalten
Klasse

formale Darstellung einer (Programmier-)Sprache und ihrer Grammatik, um Sätze in dieser Sprache sequenziell zu verarbeiten

Iterator
(Cursor)

Verhalten
Objekt

eine Möglichkeit, die Elemente eines Aggregatobjekts der Reihe nach abzuarbeiten, ohne die zugrunde liegende Speicherform kennen zu müssen

Mediator

Verhalten
Objekt

die verschiedenen Formen der Interaktion zwischen einem größeren Satz von Objekten als eigenständiges Objekt kapseln, um die Komplexität dieser Interaktion aus den Einzelobjekten selbst herauszuziehen

Memento
(Token)

Verhalten
Objekt

den internen Zustand eines Objekts auslesen und extern abspeichern, um ihn später wiederherstellen zu können

Observer
(Dependents, Publish-Subscribe)

Verhalten
Objekt

einem bestimmten Objekt (dem Subject) eine Schnittstelle hinzufügen, die eingesetzt wird, um beliebig viele andere Objekte (die Observer) über eine Zustandsänderung zu informieren, damit diese Objekte automatisch aktualisiert werden (Beispiel: in MVC kann man das Model als Subject und die Views als Observer betrachten)

State
(Objects for States)

Verhalten
Objekt

Das Verhalten eines Objekts in Abhängigkeit von seinem internen Zustand ändern; das Objekt scheint nach der Änderung einer anderen Klasse anzugehören (bekanntestes Beispiel: die verschiedenen Stadien einer TCP-Netzwerkverbindung).

Strategy
(Policy)

Verhalten
Objekt

mehrere Algorithmen aus einer Gruppe in Klassen kapseln und unter einer gemeinsamen Elternklasse verfügbar machen, um sie jederzeit austauschen zu können

Template Method

Verhalten
Klasse

die Grundstruktur eines Algorithmus in einer Elternklasse vorgeben und einige konkrete Einzelschritte in untergeordneten Klassen modifizieren

Visitor

Verhalten
Objekt

Eine Operation, die für alle Elemente einer Objektstruktur ausgeführt werden soll, wird nicht als neue Methode in jeder der Klassen implementiert, sondern als separate Klasse, damit keine nachträglichen Änderungen an den Klassen der zu untersuchenden Elemente erforderlich sind.

Beispiel: Das Singleton-Pattern

Beim Singleton-Pattern handelt es sich um ein Muster zur Verwirklichung eine Klasse, von der nur genau eine einzige Instanz existieren darf.

  • Name: Singleton
  • Einordnung: Erzeugungsmuster, Objekt
  • Absicht: Sicherstellen, dass eine Klasse nur genau eine einzige Instanz hat, und den globalen Zugriff auf diese ermöglichen
  • Alias: keines (deutsche Bezeichnung: Einzelstück)
  • Motivation: Bestimmte Objekte darf es selbst im größten System nur ein einziges Mal geben. Denken Sie beispielsweise an eine zentrale Warteschlange für Datei-, Drucker- oder Netzwerkzugriffe oder an eine globale Log-Datei für Ereignisse aus verschiedenen Programmbereichen. Praktischerweise wird ein solches Element als Klasse erstellt, die nur beim ersten Aufruf eine neue Instanz erzeugt und bei späteren Aufrufen immer wieder einen Verweis auf diese Instanz zurückgibt. So brauchen Sie beim Aufruf nicht mehr zu überprüfen, ob die Instanz bereits existiert.

    Daneben kann der Singleton auch als bessere globale Variable dienen, weil die Instanz auf einfache Weise global verfügbar ist.

  • Verwendungszweck: Benutzen Sie dieses Muster, wenn Sie eine erweiterbare Klasse brauchen, die ohne Modifikation des aufrufenden Codes nur genau eine Instanz besitzen darf.
  • Struktur: Die Struktur der Klasse Singleton wird in Abbildung 11.9 dargestellt.

    Abbildung

    Abbildung 11.9 UML-Struktur der Klasse Singleton und ihrer einzigen Instanz

  • Beteiligte: Das einzige Element ist die Klasse Singleton selbst, die auf Anforderung ihre einzige Instanz zurückgibt und gegebenenfalls neu erzeugt.
  • Zusammenspiel: Andere Klassen rufen die Methode instance() auf, um die einzige Instanz der Klasse zu erhalten.
  • Konsequenzen: Das Entwurfsmuster Singleton bietet eine Reihe von Vorteilen gegenüber anderen Lösungen. Hier die wichtigsten Vorteile:
    • Die Verwendung einer globalen Variablen wird vermieden; dies beseitigt eine potenzielle Fehlerquelle.
    • Statt genau einer Instanz können Sie mithilfe dieses Musters auch eine beliebige andere (festgelegte) Anzahl oder auch Höchstzahl von Instanzen zulassen.
    • Die Klasse bleibt erweiterbar – im Gegensatz zu anderen Lösungsansätzen für dieses Problem können problemlos abgeleitete Klassen gebildet werden.
  • Implementierung: Die Instanz wird zum statischen Attribut der Klasse selbst und mit null (noch keine Instanz vorhanden) initialisiert. Der Konstruktor wird mit der Veröffentlichungsstufe private versehen, sodass er nicht von außen aufgerufen werden kann. Die öffentliche Methode instance(), die Clients stattdessen aufrufen können, überprüft zunächst, ob die Instanz bereits erzeugt wurde; falls nicht, ruft sie den Konstruktor auf. Anschließend wird in jedem Fall eine Referenz auf die Instanz zurückgegeben.
  • Codebeispiele: Die Implementierung der Klasse Singleton ist nicht besonders umfangreich. Hier eine vollständige Java-Klasse, die dem Entwurfsmuster genügt:
    public class Singleton {

    // Die Instanz - zunächst noch nicht vorhanden
    private static Singleton singleton = null;

    // der private Konstruktor
    private Singleton() {
    }

    // die Client-Methode instance()
    public static synchronized Singleton instance() {
    // Instanz erzeugen, falls noch keine existiert
    if (singleton == null) {
    singleton = new Singleton();
    }
    // Instanz auf jeden Fall zurückgeben
    return singleton;
    }
    }

    Der Modifikator synchronized bei der Methode instance() ist wichtig: Wenn mehrere Threads die Methode parallel aufrufen, könnten sonst versehentlich doch mehrere Instanzen erzeugt werden.

    In Ruby lässt sich der Singleton beispielsweise wie folgt implementieren:

    class Singleton

    # Die Instanz als Klassenvariable,
    # zunächst leer
    @@instance = nil

    # Konstruktor und Instanzmethode clone
    # privat setzen
    private_class_method :new
    private :clone, :dup

    # Die einzige Instanz zurückgeben
    def Singleton.get_instance
    if @@instance == nil
    @@instance = new
    end
    @@instance
    end

    end

    Dies ist eine recht »paranoide« Implementierung (und somit ein relativ narrensicherer Singleton). In Ruby existieren für alle Objekte die Methoden clone und dup, mit denen sich eine Kopie einer vorhandenen Instanz – und damit ebene eine weitere Instanz der zugrunde liegenden Klasse – erzeugen lässt. Deshalb wird hier nicht nur der Konstruktor, sondern es werden auch diese Methoden privat gesetzt.

    Beachten Sie in diesem Zusammenhang, dass der eigentliche Konstruktor new und nicht etwa initialize heißt. Letzteres ist eine Methode, die bei der Objekterzeugung automatisch aufgerufen wird, um die Attribute zu initialisieren. Außerdem ist es wichtig, dass new eine Klassenmethode, clone dagegen eine Instanzmethode ist. Deshalb müssen unterschiedliche Schlüsselwörter verwendet werden, um sie zu privaten Methoden zu machen.

    Ruby macht es Ihnen allerdings noch leichter: Die Standardbibliothek enthält eine Bibliotheksdatei namens singleton. Das darin enthaltene Modul Singleton brauchen Sie nur in Ihre eigene Klassendefinition zu inkludieren, und schon ist Ihre Klasse ein threadsicherer Singleton, bei dem clone und andere »gefährliche« Methoden geschützt wurden. Die automatisch bereitgestellte Methode zur Instanzerzeugung heißt instance. Hier ein Einsatzbeispiel:

    # Bibliothek "singleton" importieren
    require "singleton"

    class MySingleton
    # Modul Singleton inkludieren
    include Singleton
    end
  • Einsatzbeispiele: unzählige – alle künstlichen »Engpässe« wie beispielsweise Warteschlangen folgen diesem Schema.
  • Querverweise: Entwurfsmuster wie Abstract Factory, Builder und Prototype lassen sich mithilfe des Singleton-Patterns implementieren.

Rheinwerk Computing - Zum Seitenanfang

11.2.3 Unit-TestsZur nächsten ÜberschriftZur vorigen Überschrift

Wenn die Zeit in Softwareentwicklungsprozessen eng wird, verzichten die Entwickler am ehesten auf ausgiebige Tests. Das ist fatal für die Qualität der veröffentlichten Anwendungen – der Extremfall ist sogenannte Banana Ware, die »grün« ausgeliefert wird und erst »beim Kunden reift«. Teure kommerzielle Software ist sogar häufiger von diesem Problem betroffen als Open-Source-Projekte, weil die Marketing- und Vertriebsabteilungen oftmals massiven Druck auf die Entwicklungsteams ausüben, um angekündigte Veröffentlichungstermine einzuhalten.

Natürlich haben Entwickler sich seit Jahrzehnten Gedanken darüber gemacht, wie sich die unbefriedigende Situation im Bereich der Softwaretests verbessern ließe. Eine wichtige Erkenntnis ist, dass Programmcode sich am exaktesten durch weiteren Programmcode überprüfen lässt. Anstatt sich also den Kopf darüber zu zerbrechen, welche Fehler auftreten könnten, und mühsam von Hand entsprechende Zustände herbeizuführen, sollten Sie einen automatisierten Test schreiben und mit verschiedenen Werten durchlaufen lassen.

Die neueste Lösung zur Testautomatisierung sind die sogenannten Unit-Tests oder auch Klassentests. Für beinahe jede wichtige Programmiersprache steht inzwischen ein xUnit-Framework zur Verfügung, das die Durchführung von Tests vereinfacht und beschleunigt und diese so zu einem integralen Bestandteil der Programmierarbeit macht. Der Klassiker ist das hier vorgestellte JUnit-Framework für Java, das von Erich Gamma und Kent Beck geschrieben wurde.

Der erste Schritt besteht darin, JUnit herunterzuladen und in Betrieb zu nehmen. Besuchen Sie die Projekt-Website http://www.junit.org, und laden Sie das aktuelle Paket herunter (zurzeit junit4.9.zip). Entpacken Sie das Archiv in ein beliebiges Verzeichnis, und erweitern Sie Ihren CLASSPATH so, dass er junit.jar aus diesem Verzeichnis enthält. Nun können Sie das Framework direkt einsetzen.

Ein einfaches Testbeispiel

Betrachten Sie als Beispiel die folgende Klasse Artikel. Sie enthält drei Methoden, die den Bruttopreis, den Mehrwertsteuerbetrag und den Nettopreis zurückgeben sollen:

public class Artikel {

private double preis;
private int mwst;

public Artikel () {
this.preis = 0;
this.mwst = 19;
}

public Artikel (double p, int m) {
this.preis = p;
this.mwst = m;
}

public double getBrutto () {
return this.preis;
}

public double getMwst () {
return this.preis / (100 + this.mwst) * this.mwst;
}

public double getNetto () {
return this.preis - this.getMwst();
}
}

Angenommen, Sie möchten die ordnungsgemäße Funktion der Methode getNetto() testen. Dazu können Sie mithilfe von JUnit folgenden Unit-Test schreiben:

import junit.framework.*;

public class ArtikelTest extends TestCase {
public ArtikelTest (String name) {
super (name);
}

public void testNetto() {
Artikel a1 = new Artikel (119, 19);
assertTrue (a1.getNetto() == 100);
}

public static void main(String[] args) {
junit.swingui.TestRunner.run (ArtikelTest.class);
}
}

Das Package junit.framework enthält die JUnit-Testklassen, die hier verwendet werden. Jeder Test sollte die Klasse TestCase erweitern. Wichtig ist hier ein Konstruktor, der einen String als Parameter erwartet. Sie können ihn, wie im Beispiel, einfach an den entsprechenden Konstruktor der übergeordneten Klasse weiterreichen.

main() ruft die Methode run() von junit.swingui.TestRunner auf, sodass beim Ausführen der Klasse automatisch die GUI-Variante von JUnit ausgeführt wird. run() sorgt automatisch für die Ausführung sämtlicher Methoden, die mit test*() beginnen. Dabei gibt es ein sehr deutliches Zeichen für Erfolg oder Misserfolg:

  • Ein grüner Balken zeigt, dass der Test bestanden wurde.
  • Ein roter Balken bedeutet Misserfolg; im unteren Fensterabschnitt werden die entsprechenden Fehlermeldungen angezeigt.

Der Test selbst funktioniert folgendermaßen: Die Annahme ist, dass ein Bruttopreis von 119 € beim üblichen Mehrwertsteuersatz von 19 % zu einem Nettopreis von 100 € führt. Also wird die Testmethode assertTrue() mit dem Vergleich zwischen dem Ergebnis von getNetto() und dem Wert 100 aufgerufen. Falls die Vermutung richtig sein sollte, führt sie zu einem grünen Balken (in Abbildung 11.10 wird dieses erfreuliche Ergebnis gezeigt). Das Framework definiert übrigens noch weitere assert*()-Methoden, weil Erfolg nicht in jedem Fall durch ein »richtiges« (oder besser »wahres«) Ergebnis angezeigt wird.

Abbildung

Abbildung 11.10 Erfolgreicher Test der Klasse »Artikel« in JUnit

Das Test-first-Verfahren

Damit der Test auf keinen Fall »vergessen« werden kann, empfiehlt es sich, ihn nicht etwa nach der Implementierung eines Features, sondern vorher zu schreiben. Das hat natürlich zur Folge, dass er zunächst nicht bestanden wird. Daraus ergibt sich die Arbeitsweise von Test-driven Development (TDD), auf Deutsch: testgetriebene Entwicklung:

  • Red – einen Test schreiben, der zunächst fehlschlägt (roter Balken)
  • Green – Code schreiben, der den Test mit den einfachsten möglichen Mitteln besteht
  • Refactor – den neuen Code durch Refactoring vernünftig integrieren, zum Beispiel unnötige Doppelanweisungsfolgen vermeiden

Statt durch viel Theorie lässt sich der Test-first-Ansatz am einfachsten an einem Beispiel zeigen: Die Klasse Artikel aus dem vorigen Abschnitt soll um eine Methode namens getDMBrutto() erweitert werden, die den Bruttopreis in DM ausgibt (praktisch für die in den vergangenen Jahren, besonders in der Vorweihnachtszeit, in manchen Geschäften veranstalteten »Zahl mit DM«-Aktionen).

Zunächst wird also ein einfacher Test geschrieben:

import junit.framework.*;

public class DMTest extends TestCase {

public DMTest (String name) {
super (name);
}


public void testDMBrutto() {
Artikel a = new Artikel (100, 19);
assertTrue (a.getDMBrutto() == 195.583);
}

public static void main(String[] args) {
junit.swingui.TestRunner.run (DMTest.class);
}
}

Da der Umrechnungsfaktor bekannt ist, lässt es sich leicht vorhersagen, welcher Wert für 100 € herauskommen muss. Diese Vermutung wird als Testfall formuliert.

Damit der Test sich überhaupt kompilieren lässt, wird zumindest ein »Dummy« der Artikel-Methode getDMBrutto() benötigt. Dieser könnte beispielsweise so aussehen:

public double getDMBrutto () {
return 0;
}

In einem so offensichtlichen Fall bräuchten Sie den Test noch nicht einmal auszuführen, um zu wissen, dass er scheitern wird. Tun Sie es trotzdem – nur so gewöhnen Sie sich an den Test-first-Ablauf.

Der nächste Schritt besteht darin, sicherzustellen, dass der Test bestanden wird. Der erste Ansatz darf ruhig eine »Brute Force«-Methode (rohe Gewalt) sein. Wenn beispielsweise 195.583 verlangt wird, kann dieser Wert einfach zurückgegeben werden:

public double getDMBrutto () {
return 195.583;
}

Nun erscheint der erwartete grüne Balken. Jetzt braucht die Methode nur noch verallgemeinert zu werden, damit sie für beliebige Werte das richtige Ergebnis liefert; das ist die für diesen Fall geeignete Form des Refactorings. So ergibt sich folgende Endfassung:

public double getDMBrutto () {
return this.preis * 1.95583;
}

Nach dem Refactoring sollten Sie den Test natürlich noch einmal durchführen, um sicherzustellen, dass Sie sich nicht vertan haben.

Auf diese Weise können Sie ein Projekt Test für Test aufbauen. In seinem Buch »Test-driven Development by Example« vergleicht Kent Beck diese Arbeitsweise mit dem Heraufziehen eines Eimers aus einem Brunnen, bei dem die Kurbelwelle mit Zähnen ausgestattet ist, die beim Loslassen einrasten. Genau dies verspricht das Test-driven Development: zu jeder Zeit »clean code that works«, also jederzeit ein so gut wie releasefähiges Projekt.

Einen guten Einstieg in die Arbeit mit JUnit bietet der lesenswerte Aufsatz »Test-Infected: Programmers Love Writing Tests« (http://junit.sourceforge.net/doc/testinfected/testing.htm), der übrigens auch mit der Offlinedokumentation von JUnit mitgeliefert wird.

Weitere Beispiele zur testgetriebenen Entwicklung finden Sie übrigens in Kapitel 18, »Webserveranwendungen«; dort kommt das PHP-Test-Framework PHPUnit zum Einsatz.



Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

<< zurück




Copyright © Rheinwerk Verlag GmbH 2013
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern


  Zum Rheinwerk-Shop
Neuauflage: IT-Handbuch für Fachinformatiker






Neuauflage: IT-Handbuch für Fachinformatiker
Jetzt Buch bestellen


 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Rheinwerk-Shop: Java ist auch eine Insel






 Java ist auch
 eine Insel


Zum Rheinwerk-Shop: Linux Handbuch






 Linux Handbuch


Zum Rheinwerk-Shop: Computer Netzwerke






 Computer Netzwerke


Zum Rheinwerk-Shop: Schrödinger lernt HTML5, CSS3 und JavaScript






 Schrödinger lernt
 HTML5, CSS3
 und JavaScript


Zum Rheinwerk-Shop: Windows 8.1 Pro






 Windows 8.1 Pro


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo