Modellierung von Software-Systemen

 

Die Modellierung der Verhaltensaspekte von Systemen und besonders der Geschäftsprozesse setzt sich immer mehr im Unternehmen durch. Dabei wird die Unified Modeling Language (UML) verwendet, um das Gesamtsystem zu visualisieren. Nur wenn nach einem bestimmten Konzept vorgegangen wird, kann man leistungsfähige Modelle realisieren. Der Beitrag zeigt die übliche Vorgehensweise wie so ein Modell iterativ entsteht.

 

Einleitung:

In den letzten Jahren hat sich die industrielle Softwareentwicklung erheblich verändert. Das hängt mit dem allgemeinen Bestreben zusammen die Entwicklung von Software zu beschleunigen und an den Endbenutzer bessere Produkte auszuliefern. Trotz der enormen Fortschritte die erreicht wurden, sind in der Industrie lange Entwicklungszyklen an der Tagesordnung. Als Ergebnis steht zusätzlich oft das Fazit das Software realisiert wurde, welche die gewünschten Anwendungsanforderungen nur adäquat löst. Hierbei werden schnell die Grenzen der traditionellen Programmierung und heutiger Entwicklungsumgebungen erreicht. Diese bekannten Grenzen haben die Softwareindustrie zu den objektorientierten Technologien (OOT) geführt, welche versprechen die Produktivität der Entwickler zu verbessern und Innovationen zu fördern. In der Realität hat sich bei Softwareprojekten jedoch gezeigt, daß der Einsatz von OOT den Software Entwicklungsprozeß zwar drastisch verbessert, aber keinesfalls alleine zum gewünschten Ziel führt. Viele sind sich einig das die bevorstehende Softwarekrise nur durch die Modellierung von Softwaresystemen aufzuhalten ist.

Die Entwickler stellten fest, daß die mitunter große Komplexität der Aufgaben in der Praxis unerwartete Probleme schafft. In der Praxis hat sich herausgestellt, daß die kaum überschaubaren Mengen und Beziehungen zwischen Klassen und Objekten ein wesentliches Problem ist. Unter anderem führt diese Komplexität der Beziehungen dazu, daß Systeme nicht mehr in einzelne für sich test- und wartbare Einheiten aufteilbar sind. Von daher ist es unverständlich warum sich CASE-Tools im Unternehmen nur zaghaft etablieren.

Denn gerade beim Entwurf einer Software Grundarchitektur können Modellierungswerkzeuge ihre Stärken ausspielen. Damit man weiß welche Klassen ein Unternehmen braucht, sind zuest die Geschäftsprozesse innerhalb des Unternehmens genau zu analysieren. So kann man dann die dazugehörigen Klassen später dann realisieren. Betriebsinterne Vorgaben und Geschäftsprozesse lassen sich notieren oder bestehende Gesetze, Verordnungen oder Verträge nach Wörtern analysieren. Meist sind es Substantivwörter, die sich als Klasse herauskristalisieren. Das übliche Vorgehensmodelle besteht aus den Phasen Analyse, Entwurf, Implementierung und Test. Die einzelnen Abläufe sind dabei stark iterativ geprägt.

 

Überschrift: Analysephase

1. Anwendungsfälle identifizieren

Während der Analysephase wird festgelegt, was das interaktive System überhaupt leisten soll. Zunächst wird überlegt, welche Interaktionen zwischen Benutzern und dem System auftreten. Diese Interaktionen werden dabei noch nicht näher betrachtet bzw. zerlegt, sondern lediglich identifiziert.  An dieser Stelle erfolgt auch noch keine Modellierung zeitlicher Abhängigkeiten. Diese abstrakte Betrachtung einer Interaktion wird als Anwendungsfall bezeichnet. Innerhalb der UML wird für die Darstellung der Anwendungsfälle eine grafische Notation verwendet.

 

Anwendungsfälle für Hotelkunde:

a) Gibt eine Reservierung auf

b) Scheckt sich beim Hotel ein

c) Scheckt sich beim Hotel aus

 

Anwendungsfälle für Hotel-Chef:

a) Ausdrucken des Belegungsplans

 

Anwendungsfälle für Haushälter:

a) Prüft ob Raum frei ist

b) Reinigt den Raum

 

Nach der Identifikation erfolgt eine stichpunkthafte, textuelle Beschreibung der Anwendungsfälle. Durch sie werden vor allem folgende Aspekte beschrieben:

1)    Aus welchen Teilen besteht der Anwendungsfall ?

2)    Wie hängen diese Teile voneinander ab ?

3)    Welche Benutzer sind am Anwendungsfall beteiligt ?

 

Diese informelle textuelle Beschreibung ist vor allem dann hilfreich, wenn Anwendungsentwickler und Anwender zusammen  die Anwendungsfälle erarbeiten.

 

Diese kann als erster Anhaltspunkt für die später zu entwickelnde Benutzerschnittstelle aufgefaßt werden. Dies bedeutet das ein Anwendungsfallbeschreibung der erste Schritt zur Entwicklung oder Generierung einer Benutzerschnittstelle ist. Wie konkret die Anwendungsfallbeschreibung ausfällt, ist sehr unterschiedlich und abhängig davon, inwiefern man bereits eine Vorstellung von dem zu entwickelnden System hat.

 

Nun werden einige der oben ermittelten Anwendungsfälle beschrieben.

Etwaige Begründungen werden ausserhalb der eigentlichen Anwendungsfallbeschreibung vermerkt. Bei diesem Beispielen ist jeweils nur ein Benutzer an einem Anwendungsfall beteiligt. Im allgemeinen können jedoch auch mehrere Benutzer an einem Anwendungsfall beteiligt sein.

 

Beschreibung:

Akteuer: Hotelchef

a) Beim System anmelden

b) Den aktuellen Systemstatus anfordern und ausdrucken

 

Akteuer: Hotelkunde bzw. Hotelangestellter

a) Beim System anmelden

b) In Reservierungsbereich wechseln und Reservierung aufnehmen

Dabei prüfen ob Kunde schon bekannt bzw. schon reserviert ist

 

Akteuer: Hotelkunde bzw. Hotelangestellter

a) Beim System anmelden

b) Die Kundendaten mit den Reservierung vergleichen ob vorhanden.

Wenn vorhanden dem Kunden ein Zimmer zuweisen.

 

2. Problembereichsmodell beschreiben

Mit Anwendungsfällen wird die Interaktion des Systems mit dem Benutzer auf sehr abstraktem Niveau beschrieben. Das Problembereichsmodell konkretisiert das interaktive Systems durch eine Beschreibung von sogenannten Geschäftsklassen, die in Beziehung zueinander stehen können. Das Problembereichsmodell besteht also aus einer Menge von Klassen und einer Menge von Beziehungen zwischen diesen Klassen. Es kann durch eine grafische Notation wie der UML mittels Klassendiagramme dargestellt werden.

 

 

 

Überschrift: Auffinden von Klassen

Das Arbeiten mit CRC-Karten ist eine relativ einfache und wirkungsvolle Methode für das Auffinden von Klassen (class) und um deren Aufgabe (Responsibility) und Beziehung zu anderen Klassen (Collaborations) darzustellen. Für jede Klasse verwendet man eine Karte, auf der die gefundenen Ergebnisse eingetragen werden und sich später besonders einfach in Gruppenszenarien durchspielen lassen. Solche Szenarien bauen vor allem auf dem interaktiven Spiel einer Gruppe auf, wo jedes Teammitglied einer oder mehrere Karten in der Hand hält und versucht, Verantwortungen (also Methoden) unter Mitwirkung anderer Klassen zu erfüllen. Beispielsweise benötigt meine Klasse eine Speichern-Methode einer anderen Klasse, um eine Datei anzulegen. Dies bedeutet die Verantwortlichkeit benötigt unter Umständen die Mithilfe eines anderen Objektes, damit die Aufgabe erfüllt werden kann. Dies trifft bei Beziehungen zu, bei anderen Diensten (Operationen) sofern notwendig. Bei den mitwirkenden Klassen ist es übersichtlicher, sie auf der gleichen Zeile auf der CRC-Karte zu notieren, auf der die entsprechende Verantwortlichkeit steht.

 

 

Speziell die Klasse ist also ein besonders mächtiges Werkzeug zur Realisierung von Abstraktionen. Der Begriff der Abstraktion beschreibt die konzeptionelle Abtrennung der Spezifikation eines Datentyps (d.h. was er ist, was er kann und welche Operationen mit ihm ausgeführt werden können) von der eigentlichen Implementierung. Diese Spezifikation stellt eine Schnittstelle dar, durch die der Programmierer den Datentyp nutzen kann.

Die Bedeutung der Abstraktion durch Klassenbildung besteht darin, daß es dem Programmierer ermöglicht wird, mit einer abstrakten Spezifikationen zu arbeiten.

 

Aus der Sicht der Datenabstraktion stellt sich ein Objekt als Instanz eines abstrakten Datentyps mit intern gekapselten Daten und Funktionen dar. Dieses Modell spiegelt sich in der Syntax und der Struktur objektorientierter Sprachen wieder. Gewöhnlich besteht die Klassendefinition aus dem Namen der Klasse, den zugehörigen Datenelementen und den Methoden (Funktionen und Operationen), die durch die Klasse bereitgestellt werden. Die eigentliche Implementierung der Methoden erfolgt getrennt hiervon und die interne Struktur der Klasse kann durch Zugriffsschlüsselworte verborgen werden.

Die Zugriffsmöglichkeit auf die Daten und Funktionen der Klasse wird durch die Schlüsselworte private, protected und public festgelegt. Die Bereiche private und protected sind für die klasseninternen Bestandteile vorgesehen. Zugriff ist bei private nur innerhalb der definierenden Klasse, bei protected zusätzlich auch aus abgeleiteten Klassen möglich. In diesen Bereichen befinden sich meist die zugrundeliegenden Datenstrukturen sowie Funktionen, die zur Verwendung durch den Klassenimplementierer, nicht aber durch den Benutzer der Klasse vorgesehen sind.

Unter public werden dagegen die für alle Klassen frei zugänglichen Komponenten angeführt. Diese stellen das externe Interface zur Benutzung der definierten Klasse dar, umfassen also üblicherweise die anwendungsbezogenen, möglichst implementationsunabhängigen Eigenschaften dar.

 

Durch die Abstraktion wird eine Klasse so allgemein wie nur möglich definiert. Die Klasse wird also mit den Aufgaben bestückt, wozu diese grundsätzlich in der Lage sein soll. Man konzentriert sich nicht auf die Details sondern auf das Grundlegende. So abstrahiert man entsprechend. Da jeder Außenstehende diese Abstraktion wegen seiner Einfachheit erfassen kann und es sofort versteht, kann die Außenwelt diese sofort für ihre Zwecke verwenden. Es werden eben die wichtigsten Aspekte einer Einheit (Klasse) von den konkreten Details getrennt und berücksichtigt damit dann nur allgemeine Sachverhalte. Dies stellt der einzige Weg dar, um jedes komplexe Sache noch mit unserem Gehirn erfassen zu können.

Hierbei benutzten wir häufig unsere Muttersprache, um dieses auszudrücken.

Jedoch kann dies auch zu Mißverständnissen verführen, da jeder eine andere Sicht auf die Dinge hat und aufgrund von gemachten Erfahrungen verschieden reagiert.

Dies würde enorme Folgen haben und erst in der Codierungsphase als Fehlerfälle sichtbar werden. Daher bedient man sich der Abstrahierung, um einen gemeinsamen Nenner zu finden. So lassen sich Ideen klassifizieren und umfassend diskutieren. In der Software Entwicklung abstrahiert man ständig, um sich seine Aufgabe zu vereinfachen. Die UML Notation ist eine ideale Basis zur wirkungsvollen präzisen Abstraktion.

 

Nun entwerfen wir die Spezifikation für unser Hotelsystem. Das komplette System kann als Objekt angesehen werden, da es alles im üblichen Sinne kapselt. So eine Reihe von Interaktionen die dieses mit der Außenwelt hat. Dabei beschreibt es intern konkret wie ein Ergebnis einer Interaktion zustande kommt. Es agiert so als Black Box, wo die Außenwelt nichts von sieht. Eine Spezifikation stellt die Sicht der Außenwelt auf das Gesamtsystem dar. In welcher Variante oder Version dieses letztlich implementiert ist, ist intern verschlossen. Wir verwenden die UML Notation um ein einfaches Hotelsystem zu beschreiben. Für eine vollständige Spezifikation zu erstellen, müssen wir die Effekte jeder Interaktionsmöglichkeit berücksichtigen. Das Ganze kann dann folgendermaßen aussehen:

 

Klasse: Hotel

Methode: einschecken

Public Raum einschecken(Kunde kd)

Vorbedingung:

Das der Kunde eine Reservierung aufgegeben hat und noch keinen Raum hat.

Nachbedingung: Das der Kunde im System einem bestimmten Raum zugewiesen ist

und der betreffende Raum nicht schon besetzt ist.

 

Klasse: Hotel

Methode: ausschecken

Public ausschecken(Kunde kd)

Vorbedingung: Das der Kunde einem Raum zugewiesen ist.

Nachbedingung: Das der Raum im System nicht mehr zugewiesen ist und

zur Zeit nicht benötigt wird.

 

Unser Beispiel verfügt jetzt lediglich über ein Verwaltungsobjekt „Hotel“ im Entwurf, welches mit seinen externen Objekte interagiert. Die weiteren Entwurfsentscheidungen sollten nicht davon abhängen, welche Klassen von Objekten sonst noch innerhalb des Systems vorkommen. Somit sind in dieser Abstraktion alle Operationen innerhalb von Hotel enthalten.

 

Mittels der obigen Vor- und Nachbedingung wird vorgegeben das etwas geschehen soll, ohne dabei anzugeben, wie dies ablaufen soll. Dabei initialisiert die Vorbedingung die Startbedingung, welche sich dann auf die Nachbedingung entsprechend auswirkt. Die Nachbedingung gibt das Ergebnis in Form einer booleschen Anweisung an.

Wenn das Hotel beispielsweise auch nicht reservierte Gäste akzeptieren will, muß die Spezifikation um eine weitere Nach- und Vorbedingungen der betreffenden Operation erweitert werden.

 

Nun betrachten wir das Gesamtmodell, dessen Entwurf immer detaillierter wird. Mittels des Model erhalten wir eine höhere Präzisionsstufe über die Spezifikation. Jedoch gibt es hier einen kleinen Widerspruch. Denn jede Nachbedingung sagt etwas über den internen Status des Systems aus. Normalerweise würde man sagen, daß dies ein Verstoß gegen das Kapselungsprinzip ist. Jedoch kann jedes System individuell entscheiden, was es von sich preis geben will. Ein Modell verfügt daher über eine Reihe von Anfragen, was das System selbst weiß. Somit existieren eine Reihe von Anfragearten(nur lesbare Anfragen) wobei jede Implementation weiß, welche Antwortmöglichkeiten es hat. Jede Implementation des Hotelsystems muß irgendwie (wir wissen jedoch nicht wie) wissen, welche Kunden und Räume das Hotel hat und welcher Raum gerade von einem Kunden belegt wird. Hierzu schreiben wir folgende Methodensignaturen und Attribute an, um solche Anfragen befriedigen zu können.

 

 

Klasse: Hotel

Klassenattribute:

kunden                  gibt die Anzahl der aktuellen Kunden zurück

räume                   gibt die Anzahl der vorhandenen Räume zurück

 

Klassenmethoden:

Setze(kunde)      einen Verweis auf einen Kunden setzen für das System

Setze(räume)      einen Verweis auf einen Raum setzen für das System

 

Hotel.belegen(kunde)    Bedingung das ein Raum frei ist

Hotel.freigeben(raum)   Bedingung das Kunde abgereist ist

 

Eine Klasse wird in der UML Notation als Rechteck symbolisiert, das mit dem entsprechenden Namen beschriftet ist. Ist die Klasse von einem anderen Stereotyp als dem Class Stereotyp, (Aufgabe der Klasse)

wird auch der Name des Stereotyps in spitze Klammern eingeschlossen, angeführt von Stereotyp Namen.(Verwaltungsklasse)

 

Um die Anfragenaufrufe leichter nachvollziehen zu können, hat es sich mittlerweile als guter Stil erwiesen, nicht einfach die Klassenattribute im Diagramm anzugeben, sondern den Zugriff auf im Diagramm diese festzuhalten. So wird transparent, wer sich dieser bedient. Dies wird aus folgender Abbildung## deutlich. Die Anfragenrichtung ist an dem Endpunkt erkennbar.

Jede Operation, welche der Designer zu implementieren hat, kann hier als Beziehung verstanden werden, zwischen der Art wie das System die Anfragen beantwortet, bevor und nachdem dieses aufgerufen wurden. Dabei ist es nicht leicht, solche Vor- und Nachbedingung genau zu spezifizieren.

 

Public ausschecken(Kunde kd) {

      If (kd.raum != nul) // Der Kunde hat einen Raum

            Raum = NIL;   // Der Raum wird nicht mehr benötigt

}

 

Die folgende Abbildung zeigt den internen Systemzustand einer Instanz von Hotel (das Hotelsystem). Dieses macht deutlich, wie das Modell zum Leben erweckt wird. Alle Objekte und Verweise sind dabei konform zum Modell.

Die Abbildung illustriert den Zustand bevor und nachdem die einschecken() Operation aufgerufen wurde. Der spätere Status durch eine dickere Linie angegeben ist.

Machen Sie sich an dieser Stelle klar, das die Verbindungen das Ergebnis darstellen, welche Fragen Sie an das System als Ganzes stellen können. Was also möglich ist, wird durch den internen Entwurf vorgegeben. Um eine Klassen jedoch wiederzuverwenden, sollten diese möglichst wenig mit anderen Klassen kommunizieren, denn je weniger eine Klasse von ihrer Umgebung weiß, desto universeller ist sie einsetzbar.

 

 

Üblicherweise wird für solch eine Sequenz von System Operationsaufrufen ein Sequenzdiagramm angelegt. Die Abbildung zeigt solch ein Sequenzdiagramm für unseren Fall hier.

Grundsätzlich sind Verweise und Objekte immer anzulegen oder zu löschen

für jede vorkommende Operation. Die Außenwelt bekommt von den einzelnen Statusübergängen nicht mit, währen die Operation abgearbeitet wird. Dies ist ausschließlich Aufgabe des Software Architekten. Hier die Möglichkeit solche Verweise formell auszudrücken:

 

Public Raum einschecken(Kunde kd) {

// Das der Kunde erwartet wird und noch keinen Raum hat

If (Kunden.findElement(kd) & kd.raum != nul) {

// Der Kunde bekommt einen freien Raum zugeteilt

this.belegen(kunde);

            raum = this.holeRaum();

            raum.setzeKunde(kunde);

            return raum; }

}

 

Lassen Sie uns nun die Anwendungsfälle erweitern. Hierbei wird ein spezielles Beispiel Szenario dargestellt, wobei die Spezifikation alle Möglichkeiten berücksichtigen muß. Durch die formelle Form kann das Gleichgewicht der Spezifikation erhalten werden. Die informelle ist leichter lesbar, die formelle weniger vieldeutig.

 

Unser letztes Klassendiagramm zeigt ein erweitertes Szenario wie das vorherige. Hier arbeitet man mit konkreten Reservierungsterminen und einem aktuellen Belegungsplan für ein Hotel. Jeder Kunde reserviert ein Zimmer für eine bestimmte Zeitspanne. Diese Daten verarbeitet das System und reagiert dann entsprechend auf weitere Kundenanfragen.

 

Jedes Modell ist nicht fertig ohne ein Verwaltungsobjekt bzw. ein Verzeichnis (Vector).Alle Verweise und Objekte werden von diesem repräsentieren. Dabei spiegeln alle Anfragen das wieder, was ein System über seine Umwelt herum weiß und worüber das Laufzeitmodell angelegt wird. Unser Modell beginnt mit einem realen Hotel, wobei jeder Raum einen Verweis auf einen konkreten Kunden hat, welcher den Raumschlüssel besitzt. Wird dies zu einer Abstraktion eines Hotel Computersystems, stellt das Verzeichnis den Verweise dar, worüber die Räume verwaltet werden.

 

Public class Hotel {

      /*** Deklarationsbereich: Klassenvariablen ***/

      Static private Vector kunde;

      Static private Vector raum;

      Static private Vector reserviert;

     

      Public Vector holeKunde() { return kunde; }

      Public void setzeKunde(Vector einKunde) { kunde = einKunde; }

      Public Vector holeRaum() { return raum; }

      Public void setzeRaum(Vector einRaum) { raum = einRaum; }

 

      Public Vector holeReserviert() { return reserviert; }

      Public void setzeReserviert(Vector eineReservierung) { reserviert = eineReservierung; }

 

Für die Schnittstelle unseres Hotels definieren wir dessen Methoden öffentlich, damit die Außenwelt die Methoden zugreifen kann.

Nur selten ist das Sichtbarkeitsattribut privat nötig, das bewirkt, daß nicht einmal abgeleitete Klassen einen Zugriff erhalten.

 

Abschluß

Die Abstraktion ist schwer sogar für besonders erfahrene Designer.

Es gibt eine große Verlockung sofort mit dem Entwurf anzufangen,

anstatt zuerst die Anforderungen des Kunden zu betrachten. Diesen Fehler begehen viele Software Architekten. Wichtig ist es, die abstrakten Aspekte zu diskutieren und aufzuschreiben. Erst dies führt zu einen einheitlichen und zuverlässigen Produkt. Dies führt auch zu einer längeren Lebenszeit des Gesamtproduktes. Neue Teammitgliedern müssen meist solche Entwürfe degenerieren, ohne dabei Klarheit über die ursprünglichen Idee der Original Architektur zur Verfügung zu haben. Nur mittels guter Abstraktion lassen sich die Ziele und Prinzipien eines Systems erhalten, lange nachdem Sie entlassen oder befördert worden sind. Grundsätzlich ist es immer lohnenswert zusätzliche Anfangsinvestition in Kauf zu nehmen, um eine längere Lebensdauer des Systems zu erreichen.

 

Bilder:

 

Abstraktion.tif: Die Abstraktion ist eines der wichtigsten Konzept um die Realwelt wirkungsvoll abzubilden.

 

Klassendiagramm.tif: Dies ist ein sehr einfaches Klassendiagramm um alles zu veranschaulichen. Es besteht lediglich aus Hotel, Kunde und Räume.

 

Erweitertes Klassendiagramm.tif: Dies ist das erweiterte Klassendiagramm,

welches jetzt auch Buchungstermine und Belegungsplan im Gesamtsystem berücksichtigt.

 

Zustände.tif: Repräsentiert das interne Gesamtmodell zur Laufzeit mit all seinen Objekten.

 

Anwendungsfälle.tif: Dies sind die Anwendungsfälle die im Fachbereich „Hotel“ grundsätzlich auftauchen.

 

Klassentyp Hotel.tif:

Über diese öffentliche Schnittstelle greift die Außenwelt auf die Funktionalitäten der Klasse zu.

 

Sequenzdiagramm: Zeit die einzelnen Interaktionen zwischen den beteiligten Objekten.