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

Inhaltsverzeichnis
Geleitwort des Fachgutachters
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

Download:
- ZIP, ca. 26,3 MB
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
Galileo Computing
ca. 1172 S., 5., aktualisierte und erweiterte Auflage, geb.
ca. 34,90 Euro, ISBN 978-3-8362-1744-6
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

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


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

UML-Geschichte

Die UML wurde in den 1990er 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.

UML in der Praxis

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

Arbeit mit ArgoUML

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

Abbildung

Abbildung 11.2 ArgoUML beim Erstellen eines Klassendiagramms

Abbildung 11.2 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.

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

Abbildung

Abbildung 11.3 Ein einfaches UML-Anwendungsfalldiagramm

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.

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

Vererbung

Die Vererbung wird durch einen Pfeil mit leerer Spitze dargestellt, der von der abgeleiteten auf die übergeordnete Klasse zeigt. In Abbildung 11.4 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.4 Ein UML-Klassendiagramm mit einer Elternklasse und zwei abgeleiteten Klassen

SonstigeBeziehungen

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.5 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.5 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 mit Hilfe von durchgezogenen Pfeilen gekennzeichnet, Nachrichten durch gestrichelte Pfeile. Abbildung 11.6 zeigt das einfache Beispiel eines Einkaufsvorgangs.

Abbildung

Abbildung 11.6 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.7 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.7 UML-Aktivitätsdiagramm des Anwendungsfalls »Information«


Galileo 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 1970er 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

Muster-Katalogisierung

Pattern-Kataloge wie derjenige im 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 dreiundzwanzig 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 Tabelle 11.2 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 obigen 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. In den folgenden Abschnitten finden Sie zwei vollständig ausgeführte Beispiele für 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 instantiierende 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 Performance-Grü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 sequentiell zu verarbeiten
Iterator
(Cursor)
Verhalten
Objekt
eine Möglichkeit, die Elemente eines Aggregatobjekts der Reihe nach abzuarbeiten, ohne die zugrundeliegende 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.8 dargestellt.
Abbildung

Abbildung 11.8 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 potentielle Fehlerquelle.
  • Statt genau einer Instanz können Sie mit Hilfe 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, so dass 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 zugrundeliegenden Klasse – erzeugen lässt. Deshalb wird hier nicht nur der Konstruktor, sondern 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 mit Hilfe des Singleton-Patterns implementieren.

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

Motivation

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.

JUniteinrichten

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();
}
}

Ein Test

Angenommen, Sie möchten die ordnungsgemäße Funktion der Methode getNetto() testen. Dazu können Sie mit Hilfe 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.

Testausführung

main() ruft die Methode run() von junit.swingui.TestRunner auf, so dass 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.
Abbildung

Abbildung 11.9 Erfolgreicher Test der Klasse Artikel in JUnit

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

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), zu 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

Test-first-Beispiel

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 besonders in der Vorweihnachtszeit manchmal noch anzutreffenden »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 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;
}

Red

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.

Green

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;
}

Refactor

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 geliefert wird.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.







<< zurück




Copyright © Galileo Press 2011
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.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de


  Zum Katalog
Zum Katalog: IT-Handbuch für Fachinformatiker






IT-Handbuch für Fachinformatiker
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Java ist auch eine Insel






 Java ist auch
 eine Insel


Zum Katalog: Android 3






 Android 3


Zum Katalog: Linux






 Linux


Zum Katalog: Ubuntu GNU/Linux






 Ubuntu
 GNU/Linux


Zum Katalog: Windows Server 2008 R2






 Windows Server
 2008 R2


Zum Katalog: PHP & MySQL






 PHP & MySQL


Zum Katalog: Visual C# 2010






 Visual C# 2010


Zum Katalog: C von A bis Z






 C von A bis Z


Zum Katalog: C++ von A bis Z






 C++ von A bis Z


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo