Aufgabe: Klassendesign
Diese Aufgabe definiert, wie die Klassenstruktur eines Subsystems oder einer Komponente entworfen wird.
Disziplinen: Analyse und Design
Zweck
  • Sicherstellen, dass die Klasse das für die Anwendungsfallrealisierungen erforderliche Verhalten liefern
  • Sicherstellen, dass genügend Informationen bereitgestellt werden, um die Klasse eindeutig zu implementieren
  • Erfüllung nicht funktionaler Anforderungen, die mit der Klasse in Verbindung stehen
  • Integration des von der Klasse verwendeten Designmechanismus
Beziehungen
Hauptbeschreibung

Klassen sind die Arbeitspferde des Design. Sie sind es, die die eigentliche Arbeit im System ausführen. Andere Designelemente wie Subsysteme, Pakete und Kollaborationen beschreiben, wie Klassen gruppiert werden oder miteinander interagieren.

Kapseln sind stereotype Klassen, mit denen parallele Ausführungs-Threads in Echtzeitsystemen dargestellt werden. In solchen Fällen sind andere Designklassen passive Klassen, die in dem von den aktiven Kapseln unterstützten Ausführungskontext verwendet werden. Wenn der Softwarearchitekt und der Designer sich gegen einen Designansatz auf der Basis von Kapseln entscheiden, kann paralleles Verhalten trotzdem modelliert werden und zwar mit Hilfe aktiver Klassen.

Aktive Klassen sind Designklassen, die das Verhalten der passiven Klassen koordinieren und steuern. Eine aktive Klasse ist eine Klasse, deren Instanzen aktive Objekte mit einem jeweils eigenen Steuer-Thread sind.

Schritte
Designmuster und -mechanismen verwenden

Verwenden Sie Designmuster und -mechanismen, die für die zu entwerfende Klasse bzw. Funktionalität geeignet sind und den Designrichtlinien für das Projekt entsprechen.

Durch die Integration eines Musters und/oder Mechanismus werden viele der nachfolgend in dieser Aufgabe beschriebenen Schritte (neue Klassen, Operationen, Attribute und Beziehungen hinzufügen) effizient und trotzdem gemäß den im Muster bzw. Mechanismus definierten Regeln ausgeführt.

Beachten Sie, dass Muster und Mechanismen gewöhnlich während der Weiterentwicklung des Designs und nicht im ersten Schritt dieser Aufgabe integriert werden. Sie werden außerdem häufig auf mehrere Klassen und nicht nur auf eine einzelne Klasse angewendet.

Erste Designklassen erstellen

Erstellen Sie eine oder mehrere erste Designklassen für die in dieser Aufgabe zu erstellende Analyseklasse und ordnen Sie Trace-Abhängigkeiten zu. Die in diesem Schritt erstellten Designklassen werden in den nachfolgenden Schritten präzisiert, angepasst, aufgeteilt oder zusammengefasst, wenn ihnen verschiedene Designmerkmale wie Operationen, Methoden und eine Zustandsmaschine zugeordnet wurden, die beschreiben, wie die Analyseklasse entworfen ist.

Je nach Typ der zu erstellenden Analyseklasse (Grenzklasse, Entitätsklasse oder Steuerungsklasse) stehen bestimmte Strategien zur Verfügung, die Sie zum Erstellen erster Designklassen angewendet werden können.

Grenzklassen entwerfen

Grenzklassen stellen Schnittstellen zu Benutzern oder anderen Systemen dar.

In der Regel werden Grenzklassen, die Schnittstellen zu anderen Systemen darstellen, als Subsysteme modelliert, weil sie häufig ein komplexes internes Verhalten haben. Wenn das Schnittstellenverhalten einfach ist (z. B. nur als Durchgang zu einer vorhandenen API zum externen System auftritt), können Sie die Schnittstelle mit einer oder mehreren Designklassen darstellen. Wenn Sie diesen Weg wählen, verwenden Sie pro Protokoll, Schnittstelle oder API eine Designklasse und dokumentieren Sie spezielle Anforderungen bezüglich Standards, die Sie in den Sonderanforderungen der Klasse verwendet haben.

Für Grenzklassen, die Schnittstellen zu Benutzern darstellen, gilt im Allgemeinen die Regel, dass für jedes Fenster bzw. jedes Formular in der Benutzerschnittstelle eine Grenzklasse verwendet wird. Deshalb sind die Zuständigkeiten der Grenzklassen auf einer relativ hohen Ebene angesiedelt und müssen in diesem Schritt präzisiert und detailliert beschrieben werden. Zusätzliche Modelle oder Prototypen der Benutzerschnittstelle können eine weitere Eingabequelle sein, die in diesem Schritt berücksichtigt werden muss.

Das Design von Grenzklassen richtet sich nach den Entwicklungstools für Benutzerschnittstellen (UI), die im Projekt zur Verfügung stehen. Bei der Verwendung aktueller Technologie wird die UI gewöhnlich direkt im Entwicklungstool visuell erstellt. Dabei werden die UI-Klassen, die dem Design der Steuerungsklassen und Entitätsklassen zugeordnet werden müssen, automatisch erstellt. Wenn die UI-Entwicklungsumgebung die unterstützenden Klassen automatisch erstellt, muss es die UI implementieren, und daher besteht kein Bedarf, diese im Design zu berücksichtigen. Sie entwerfen nur das, was die Entwicklungsumgebung nicht für Sie erstellt.

Entitätsklassen entwerfen

Während der Analyse stellen Entitätsklassen bearbeitete Informationseinheiten dar. Sie sind häufig passiv und persistent und können mit den Analysemechanismen für Persistenz identifiziert und zugeordnet werden. Die Details des Designs eines datenbankbasierten Persistenzmechanismus werden mit der Aufgabe: Datenbankdesign abgedeckt. Leistungsaspekte können eine Umstrukturierung persistenter Klassen erfordern, was zu Änderungen am Designmodell führt. Diese werden gemeinsam von Datenbankdesigner und Designer beschlossen.

Eine allgemeinere Erläuterung von Designaspekten für persistente Klassen finden Sie unter der Überschrift Persistente Klassen identifizieren.

Steuerungsklassen entwerfen

Ein Steuerungsobjekt ist für die Verwaltung des Ablaufs eines Anwendungsfalls verantwortlich und koordiniert somit die meisten Aktionen des Anwendungsfalls. Steuerungsobjekte kapseln Logik, die in keinem besonderen Zusammenhang mit den Benutzerschnittstellenaspekten (Grenzobjekte) oder Daten-Engineering-Aspekten (Entitätsobjekte) stehen. Diese Logik wird auch Anwendungslogik oder Geschäftslogik genannt.

Berücksichtigen Sie beim Design von Steuerungsklassen folgende Punkte:

  • Komplexität - Unkompliziertes Steuerungs- oder Koordinationsverhalten kann mit Grenz- oder Entitätsklassen bearbeitet werden. Mit steigender Komplexität der Anwendung zeichnen sich jedoch signifikante Nachteile dieses Ansatzes auf, wie z. B.:
  • Das Koordinationsverhalten des Anwendungsfalls wird in die Benutzerschnittstelle eingebettet, wodurch das System schwieriger zu ändern ist.
  • Eine Wiederverwendung derselben UI in verschiedenen Anwendungsfallrealisierungen ist nicht ohne Weiteres möglich.
  • Die Benutzerschnittstelle wird mit zusätzlicher Funktionalität belastet, was sich auf die Leistung auswirkt.
  • Die Entitätsobjekte können mit anwendungsspezifischem Verhalten belastet werden, was ihre Allgemeingültigkeit beeinträchtigt.

Zur Vermeidung solcher Probleme können Steuerungsklassen eingeführt werden, die Verhalten bereitstellen, das mit der Koordination von Ereignisabläufen in Zusammenhang steht.

  • Änderungswahrscheinlichkeit - Wenn die Wahrscheinlichkeit, dass sich Ereignisabläufe ändern, gering ist oder die Kosten zu vernachlässigen sind, können sich die Sonderausgaben und die Komplexität zusätzlicher Steuerungsklassen nicht rechtfertigen.
  • Verteilung und Leistung - Der Bedarf, Teile der Anwendung auf unterschiedlichen Knoten oder in unterschiedlichen Prozessbereichen auszuführen, bringt den Bedarf an speziellen Designmodellelementen mit sich. Diese Spezialisierung wird häufig durch das Hinzufügen von Steuerungsobjekten und die Verteilung des Verhaltens von den Grenz- und Entitätsklassen auf die Steuerungsklassen erreicht. Dadurch wird erreicht, dass die Grenzklassen nur noch Benutzerschnittstellenservices, die Entitätsklassen nur noch Datenservices und die Steuerungsklassen die restlichen Services bereitstellen.
  • Transaktionsmanagement - Die Verwaltung von Transaktionen ist eine klassische Koordinationsaktivitäten. Ohne ein Framework, das das Transaktionsmanagement unterstützt, müssten eine oder mehrere Transaktionsmanagerklassen miteinander interagieren, um sicherzustellen, dass die Integrität der Transaktion gewahrt bleibt.

Wenn die Steuerungsklasse in den beiden letzten Fällen einen separaten Steuerungs-Thread darstellt, würde es sich anbieten, eine aktive Klasse für die Modellierung des Steuerungs-Thread zu verwenden. In Echtzeitsystemen ist die Verwendung von Kapseln der bevorzugte Modellierungsansatz.

Persistente Klassen identifizieren

Klassen, die ihren Zustand auf einem permanenten Medium speichern müssen, werden als persistente Klassen bezeichnet. Der Zustand muss möglicherweise gespeichert werden, um Klasseninformationen permanent aufzuzeichnen, um eine Sicherung im Fall eines Systemausfalls zu haben oder um Informationen auszutauschen. Eine persistente Klasse kann persistente und transiente Instanzen haben. Eine Klasse als persistent zu bezeichnen, bedeutet lediglich, dass einige Instanzen der Klasse persistent sein müssen.

Integrieren Sie Designmechanismen für die Persistenzmechanismen, die während der Analyse gefunden werden. Je nachdem, was die Klasse erfordert, könnte der Analysemechanismus für Persistenz beispielsweise durch einen der folgenden Designmechanismen realisiert werden:

  • Speicher im Hauptspeicher
  • Flash-Karte
  • Binärdatei
  • Datenbankmanagementsystem (DBMS)

Persistente Objekte können nicht nur von Entitätsklassen abgeleitet werden. Persistente Objekte können auch erforderlich sein, um nicht funktionale Anforderungen im Allgemeinen zu bearbeiten. Beispiele hierfür sind persistente Objekte, die erforderlich sind, um für die Prozesssteuerung relevante Informationen oder Zustandsinformationen zwischen Transaktionen zu verwalten.

Die Identifizierung persistenter Klassen dient dazu, den Datenbankdesigner darüber zu informieren, dass die Klasse spezielle Aufmerksamkeit in Bezug auf die physischen Speichermerkmale erfordert. Außerdem erhält der Softwarearchitekt eine Benachrichtigung, dass die Klasse persistent sein muss, und der für den Persistenzmechanismus verantwortliche Designer eine Benachrichtigung, dass die Instanzen der Klasse persistent sein müssen.

Da eine eine koordinierte Persistenzstrategie erforderlich ist, muss der Datenbankdesigner unter Verwendung eines Persistenz-Framework persistente Klassen in der Datenbank abbilden. Wenn das Projekt ein Persistenz-Framework entwickelt, muss der Framework-Entwickler sich außerdem mit den Persistenzanforderungen von Designklassen auseinandersetzen. Um diese Personen mit den benötigten Informationen zu versorgen, reicht es an dieser Stelle aus, darauf hinzuweisen, dass die Klasse persistent ist, oder genauer gesagt, dass die Instanzen der Klasse persistent sind.

Klassensichtbarkeit definieren

Bestimmen Sie für jede Klasse die Sichtbarkeit im übergeordneten Paket. Eine öffentliche Klasse kann außerhalb des übergeordneten Pakets referenziert werden. Eine private Klasse (oder eine Klasse, deren Sichtbarkeit mit Implementierung festgelegt ist) kann nur von Klassen in demselben Paket referenziert werden.

Operationen definieren

Operationen identifizieren

Gehen Sie wie folgt vor, um die Operationen in Designklassen zu identifizieren:

  • Studieren Sie die Zuständigkeiten jeder Analyseklasse und erstellen Sie für jede Zuständigkeit eine Operation. Verwenden Sie die Beschreibung der Zuständigkeit als Ausgangsbeschreibung für die Operation.
  • Studieren Sie die Anwendungsfallrealisierungen in der Klasse partizipiert, um festzustellen, wie die Operationen von den Anwendungsfallrealisierungen verwendet werden. Erweitern Sie die Operationen, eine Anwendungsfallrealisierung nach der anderen, und präzisieren Sie die Operationen, ihre Beschreibungen, ihre Rückgabetypen und ihre Parameter. Die Anforderungen jeder Anwendungsfallrealisierung in Bezug auf die Klassen werden in Textform im Ereignisablauf der Anwendungsfallrealisierung beschrieben.
  • Studieren Sie die Sonderanforderungen, um sicherzustellen, dass keine impliziten Anforderungen an die Operation, die dort beschrieben sind, vergessen wurden.

Operationen sind erforderlich, um die Nachrichten zu unterstützen, die in den Ablaufdiagrammen erscheinen, weil Scripts (temporäre Nachrichtenspezifikationen, die noch keinen Operationen zugeordnet wurden) das Verhalten beschreiben, das von der Klasse erwartet wird. Abbildung 1 zeigt ein Beispiel für ein Ablaufdiagramm.

Im Begleittext beschriebenes Diagramm

Abbildung 1: Nachrichten bilden die Basis für die Identifizierung von Operationen

Anwendungsfallrealisierungen können nicht genügend Informationen enthalten, um alle Operationen zu identifizieren. Zum Ermitteln der verbleibenden Operationen sollten Sie die folgenden Fragen verwenden:

  • Gibt es eine Möglichkeit, eine neue Instanz der Klasse zu initialisieren, z. B. auch durch Verbindung der Klasse mit Instanzen anderer Klassen, denen sie zugeordnet ist?
  • Muss ein Test durchgeführt werden, um festzustellen, ob zwei Instanzen der Klasse identisch sind?
  • Muss eine Kopie einer Klasseninstanz erstellt werden?
  • Werden Operationen in der Klasse von Mechanismen benötigt? Ein Garbage-Collection-Mechanismus kann beispielsweise voraussetzen, dass ein Objekt in der Lage ist, alle Referenzen auf alle anderen Objekten zu löschen, damit nicht verwendete Ressourcen freigegeben werden können.

Definieren Sie keine Operationen, die lediglich Werte von öffentlichen Attributen abrufen (get) und setzen (set). Weitere Informationen hierzu finden Sie in Attribute definieren und Assoziationen definieren. Gewöhnlich werden diese von Codegenerierungsfunktionen generiert und müssen nicht explizit definiert werden.

Operationen benennen und beschreiben

Verwenden Sie für die Benennung von Operationen, Rückgabetypen und Parametern sowie ihren Typen die Namenskonventionen der Implementierungssprache. Diese sind in den projektspezifischen Richtlinien beschrieben.

Sie müssen für jede Operation Folgendes definieren:

  • Operationsname - Verwenden Sie einen kurzen Namen, der das Ergebnis der Operation beschreibt.
    • Die Namen von Operationen müssen der Syntax der Implementierungssprache entsprechen. Beispiel: position_suchen ist zwar für C++ und Visual Basic akzeptabel, aber nicht für Smalltalk (in dieser Sprache werden keine Unterstreichungszeichen verwendet). Ein Name, der sich für alle Sprachen eignet, wäre PositionSuchen.
    • Vermeiden Sie Namen, die implizieren, wie die Operation ausgeführt wird. Beispielsweise eignet sich Mitarbeiter.loehne() besser als Mitarbeiter.LoehneBerechnen(), da letzterer darauf hinweise, dass eine Berechnung durchgeführt wird. Die Operation könnte auch nur einfach einen Wert aus der Datenbank zurückgeben.
    • Der Name einer Operation muss den Zweck der Operation deutlich machen. Vermeiden Sie unspezifische Namen wie getDaten, die das Ergebnis der Operation nicht beschreiben. Verwenden Sie einen Namen, der exakt zeigt, was erwartet wird, z. B. getAdresse. Noch besser ist, als Operationsnamen den Namen der Eigenschaft zu verwenden, die zurückgegeben oder gesetzt wird. Wenn die Operation einen Parameter hat, wird die Eigenschaft gesetzt. Wenn die Operation keinen Parameter hat, wird die Eigenschaft abgerufen. Beispiel: Die Operation adresse gibt die Adresse eines Kunden zurück, wohingegen adress(eineZeichenfolge) die Adresse des Kunden setzt oder ändert. Die get- und set-Spezifik der Operation ergibt sich implizit aus der Deklaration der Operation.
    • Operationen, die konzeptionell identisch sind, sollten denselben Namen haben, selbst wenn sie von unterschiedlichen Klassen definiert werden, auf unterschiedliche Arten implementiert werden oder eine unterschiedliche Anzahl von Parametern haben. Eine Operation, die ein Objekt erstellt, sollte beispielsweise in allen Klassen denselben Namen haben.
    • Wenn Operationen in mehreren Klassen dieselbe Deklaration haben, muss die Operation dieselbe Art von Ergebnis für das jeweilige Empfängerobjekt zurückgeben. Dies ist ein Beispiel für das Konzept der Polymorphie, das sagt, dass unterschiedliche Objekte in ähnlicher Form auf dieselbe Nachricht antworten sollen. Beispiel: Die Operation name soll den Namen des Objekts zurückgeben, unabhängig davon, wie der Name gespeichert ist oder abgeleitet wird. Die Einhaltung dieses Prinzips macht das Modell verständlicher.
  • Rückgabetyp - Der Rückgabetyp muss die Klasse des Objekts sein, dass von der Operation zurückgegeben wird.
  • Kurzbeschreibung - So aussagefähig Sie den Namen einer Operation auch gestalten, ist er häufig nur wenig hilfreich, um den Zweck einer Operation zu verstehen. Weisen Sie der Operation eine Kurzbeschreibung zu, die aus einigen wenigen Sätzen besteht und aus der Sicht des Benutzers geschrieben ist.
  • Parameter - Erstellen Sie für jeden Parameter einen kurzen, beschreibenden Namen, legen Sie die zugehörige Klasse fest und weisen Sie ihm eine Kurzbeschreibung zu. Denken Sie bei der Spezifikation von Parametern daran, dass die Wiederverwendbarkeit umso höher ist, je weniger Parameter definiert sind. Eine kleine Anzahl von Parametern macht die Operation verständlicher und erhöht die Wahrscheinlichkeit, ähnliche Operationen zu finden. Möglicherweise müssen Sie eine Operation mit vielen Parametern in mehrere Operationen aufteilen. Die Operation muss für diejenigen, von denen sie verwendet wird, verständlich sein. Die Kurzbeschreibung sollte Folgendes enthalten:
    • Die Bedeutung der Parameter, falls diese nicht aus den Namen hervorgeht.
    • Ob der Parameter nach Wert oder nach Referenz übergeben wird.
    • Parameter, für die Werte angegeben werden müssen.
    • Optionale Parameter und ihre Standardwerte, falls kein Wert angegeben wird.
    • Gültige Bereiche für Parameter, sofern anwendbar.
    • Was ist der Operation ausgeführt wird.
    • Welche Parameter, die nach Referenz übergeben werden, von der Operation geändert werden.

Nachdem Sie die Operationen definiert haben, vervollständigen Sie die Ablaufdiagramme mit den Informationen zu den Operationen, die für jede Nachricht aufgerufen werden.

Weitere Informationen hierzu finden Sie im Abschnitt Richtlinie für Arbeitsergebnis: Designklasse.

Sichtbarkeit der Operationen definieren

Identifizieren Sie für jede Operation die Exportsichtbarkeit der Operation. Sie können zwischen den folgenden Optionen wählen:

  • Public (Öffentlich) - Die Operation ist auch für andere Modellelemente abgesehen von der Klasse sichtbar.
  • Implementation (Implementierung) - Die Operation ist nur innerhalb der Klasse sichtbar.
  • Protected (Geschützt) - Die Operation ist nur für die Klasse selbst, ihre Unterklassen und für so genannte Friends der Klasse (sprachenabhängig) sichtbar.
  • Private (Privat) - Die Operation ist nur für die Klasse selbst und für so genannte Friends der Klasse sichtbar.

Wählen Sie die Sichtbarkeitsoption aus, die am restriktivsten ist und trotzdem die Zielsetzungen der Operationen unterstützt. Schauen Sie sich dazu die Ablaufdiagramme an und bestimmen Sie für jede Nachricht, ob die Nachricht von einer Klasse außerhalb des Empfängerpakets (erfordert die Sichtbarkeitsoption public), aus dem Paket selbst (erfordert die Sichtbarkeitsoption implementation), von einer Unterklasse (erfordert die Sichtbarkeitsoption protected) oder von der Klasse selbst oder einer Friend-Klasse stammt (erfordert die Sichtbarkeitsoption private).

Klassenoperationen definieren

Meistens sind Operationen Instanzoperationen, d. h. sie werden in Instanzen der Klasse ausgeführt. Manchmal gilt eine Operation jedoch für alle Instanzen der Klasse und ist damit eine klassenbezogene Operation. Der Empfänger der Klassenoperation ist eine Instanz einer Metaklasse (Beschreibung der Klasse selbst) und keine spezifische Instanz der Klasse. Beispiele für Klassenoperationen sind Nachrichten, die neue Instanzen erstellen (instanzieren), die alle Instanzen einer Klasse zurückgeben.

Die Operationszeichenfolge ist unterstrichen, um sie als klassenbezogene Operation zu kennzeichnen.

Methoden definieren

Eine Methode spezifiziert die Implementierung einer Operation. In vielen Fällen, in denen das von der Operation geforderte Verhalten ausreichend durch den Operationsnamen, die Beschreibung und die Parameter definiert wird, werden Methoden direkt in der Programmiersprache implementiert. Wenn die Implementierung einer Operation die Verwendung eines speziellen Algorithmus oder mehr Informationen erfordert, als die Beschreibung der Operation enthält, ist eine gesonderte Methodenbeschreibung erforderlich. Die Methode beschreibt, wie die Operation funktioniert und nicht nur, was sie tut.

Die Methode sollte Folgendes beschreiben:

  • Wie Operationen implementiert werden.
  • Wie Attribute implementiert und zur Implementierung von Operationen verwendet werden.
  • Wie Beziehungen implementiert und zur Implementierung von Operationen verwendet werden.

Die Anforderungen variieren von Fall zu Fall, aber die Methodenspezifikationen für eine Klasse müssen unter allen Umständen Folgendes beschreiben:

  • Was in Bezug auf die Anforderungen unternommen wird.
  • Welche anderen Objekte und Operationen verwendet werden.

Spezifischere Anforderungen könnten sich auf Folgendes beziehen:

  • Wie Parameter implementiert werden.
  • Welche, sofern überhaupt, speziellen Algorithmen verwendet werden.

Ablaufdiagramme sind hierfür eine wichtige Quelle. Aus den Ablaufdiagrammen geht klar hervor, welche Operationen in anderen Objekten verwendet werden, wenn eine Operation ausgeführt wird. Welche Operationen in anderen Objekten verwendet werden, muss für eine vollständige Implementierung einer Operation spezifiziert werden. Die Erstellung einer vollständigen Methodenspezifikation setzt demzufolge voraus, dass Sie die Operationen für die beteiligten Objekte identifizieren und die entsprechenden Ablaufdiagramme untersuchen.

Zustände definieren

Bei einigen Operationen richtet sich ihr Verhalten nach dem jeweiligen Zustand des Empfängerobjekts. Eine Zustandsmaschine ist ein Tool, das die Zustände beschreibt, die ein Objekt annehmen kann, und die Ereignisse, die bewirken, dass das Objekt von einem Zustand in einen anderen wechselt. Informationen hierzu finden Sie im Abschnitt Technik: Zustandsdiagramm. Zustandsmaschinen sind hilfreich für die Beschreibung aktiver Klassen. Die Verwendung von Zustandsmaschinen ist insbesondere wichtig, wenn das Verhalten von Kapseln definiert wird.

Ein Beispiel für eine einfache Zustandsmaschine wird in Abbildung 2 gezeigt.

Im Begleittext beschriebenes Diagramm

Abbildung 2: Einfaches Zustandsdiagramm für eine Zapfsäule

Jedes Ereignis für einen Statusübergang kann einer Operation zugeordnet werden. Je nach Objektzustand kann die Operation ein anderes Verhalten haben. Dies wird in den Übergangsereignissen beschrieben.

Die Methodenbeschreibung für die zugeordnete Operation muss mit den zustandsspezifischen Informationen aktualisiert werden. Für jeden relevanten Zustand muss angegeben werden, was die Operation tun soll. Zustände werden häufig mit Attributen dargestellt. Die Zustandsdiagramme dienen als Eingabe für die Attributidentifikation.

Weitere Informationen hierzu finden Sie im Abschnitt Technik: Zustandsdiagramm.

Attribute definieren

Während der Definition von Methoden und der Identifizierung von Zuständen werden die Attribute ermittelt, die die Klasse erfordert, um ihre Operationen auszuführen. Attribute sind ein Informationsspeicher für die Klasseninstanz und werden häufig verwendet, um den Zustand der Klasseninstanz darzustellen. Für jede Information, die die Klasse selbst verwaltet, wird ein Attribut verwendet. Definieren Sie für jedes Attribut Folgendes:

  • Den Namen, bei dem die Namenskonventionen der Implementierungssprache und des Projekts eingehalten werden müssen.
  • Den Typ, bei dem es sich um einen Elementardatentyp handelt, der von der Implementierungssprache unterstützt wird.
  • Den Standard- bzw. Anfangswert, mit dem das Attribut initialisiert wird, wenn neue Instanzen der Klasse erstellt werden.
  • Die Sichtbarkeit, bei der Sie zwischen den folgenden Werten wählen können:
    • Public (Öffentlich): Das Attribut ist innerhalb und außerhalb des Pakets, das die Klasse enthält, sichtbar.
    • Protected (Geschützt): Das Attribut ist nur für die Klasse selbst, ihre Unterklassen und für so genannte Friends der Klasse (sprachenabhängig) sichtbar.
    • Private (Privat): Das Attribut ist nur für die Klasse selbst und für so genannte Friends der Klasse sichtbar.
    • Implementation (Implementierung): Das Attribut ist nur für die Klasse selbst sichtbar.
  • Persistente Klassen, d. h. ob das Attribut persistent (Standardeinstellung) oder transient ist. Selbst wenn die Klasse selbst persistent ist, müssen nicht alle Attribute der Klasse persistent sein.

Stellen Sie sicher, dass alle Attribute erforderlich sind. Attribute müssen gerechtfertigt sein. Attribute werden häufig in einem frühen Stadium des Prozess hinzugefügt und bleiben aus Unachtsamkeit bestehen, obwohl sie nicht mehr benötigt werden. Zusätzliche Attribute, multipliziert mit Tausenden oder Millionen von Instanzen, können nachteilige Auswirkungen auf die Leistung und den Speicherbedarf eines Systems haben.

Weitere Informationen zu Attributen finden Sie im Abschnitt Attribute im Abschnitt Richtlinie für Arbeitsergebnis: Designklasse.

Abhängigkeiten definieren

Stellen Sie sich zu jedem Fall, in dem eine Kommunikation zwischen Objekten erforderlich ist, die folgenden Fragen:

  • Wird die Referenz auf den Empfänger als Parameter an die Operation übergeben? Wenn ja, richten Sie eine Abhängigkeit zwischen den Sender- und Empfängerklassen in einem Klassendiagramm ein, das die beiden Klassen enthält. Wenn ein Kommunikationsdiagramm für Interaktionen verwendet wird, müssen Sie außerdem die Sichtbarkeit der Verbindung qualifizieren und auf parameter setzen.
  • Ist der Empfänger global? Wenn ja, richten Sie eine Abhängigkeit zwischen den Sender- und Empfängerklassen in einem Klassendiagramm ein, das die beiden Klassen enthält. Wenn ein Kommunikationsdiagramm für Interaktionen verwendet wird, müssen Sie außerdem die Sichtbarkeit der Verbindung qualifizieren und auf global setzen.
  • Ist der Empfänger ein temporäres Objekt, das während der Operation selbst erstellt und wieder gelöscht wird? Wenn ja, richten Sie eine Abhängigkeit zwischen den Sender- und Empfängerklassen in einem Klassendiagramm ein, das die beiden Klassen enthält. Wenn ein Kommunikationsdiagramm für Interaktionen verwendet wird, müssen Sie außerdem die Sichtbarkeit der Verbindung qualifizieren und auf local setzen.

Verbindungen, die auf diese Weise modelliert werden, sind transiente Verbindungen, die nur für eine begrenzte Dauer im spezifischen Kontext der Kollaboration existieren. In diesem Sinne sind sie Instanzen der Assoziationsrolle in der Kollaboration. Die Beziehung in einem Klassenmodell (d. h. kontextunabhängig) muss jedoch, wie bereits erwähnt, eine Abhängigkeit sein. [RUM98] definiert eine transiente Verbindung wie folgt: "Es ist möglich, solche Verbindungen generell als Assoziationen zu modellieren, aber in diesem Fall müssen die Bedingungen in den Assoziationen sehr allgemein beschrieben werden und verlieren damit einen Großteil ihrer Präzision bezüglich der Einschränkung von Objektkombinationen.". In dieser Situation ist die Modellierung einer Abhängigkeit weniger wichtig als die Modellierung der Beziehung in der Kollaboration, da die Abhängigkeit die Beziehung nicht vollständig beschreibt, sondern nur, dass sie existiert.

Assoziationen definieren

Assoziationen sind ein Mechanismus, der die Kommunikation zwischen Objekten ermöglicht. Sie stellen Objekten einen Kanal zur Verfügung, über den Nachrichten gesendet werden können. Außerdem dokumentieren sie die Abhängigkeiten zwischen Klassen und veranschaulichen, dass sich Änderungen in einer Klasse auf viele andere Klassen auswirken können.

Lesen Sie die Methodenbeschreibungen für jede Operation, um zu verstehen, wie Instanzen der Klasse mit anderen Objekten kommunizieren und kollaborieren. Wenn ein Objekt eine Nachricht an ein anderes Objekt senden möchte, muss es eine Referenz auf den Empfänger der Nachricht haben. Ein Kommunikationsdiagramm (eine alternative Darstellung eines Ablaufdiagramms) zeigt die Objektkommunikation in Form von Verbindungen. Ein Beispiel für ein Kommunikationsdiagramm sehen Sie in Abbildung 3.

Im Begleittext beschriebenes Diagramm

Abbildung 3: Beispiel für ein Kommunikationsdiagramm

Assoziationen und Aggregationen definieren

Die verbleibenden Nachrichten verwenden entweder eine Assoziation oder eine Aggregation, um die Beziehung zwischen Instanzen von zwei miteinander kommunizierenden Klassen zu spezifizieren. Informationen zur Auswahl der angemessenen Darstellung finden Sie im Abschnitt Technik: Assoziation und im Abschnitt Technik: Aggregation. Definieren Sie für beide Assoziationen die Sichtbarkeit der Verbindung in Kommunikationsdiagrammen auf field. Weitere Aufgaben:

  • Konfigurieren Sie die Navigierbarkeit von Assoziationen und Aggregationen. Überlegen Sie hierbei, welche Navigationsfähigkeiten in den Verbindungsinstanzierungen in den Interaktionsdiagrammen erforderlich sind. Da Navigierbarkeit standardmäßig eingestellt ist, müssen Sie nur noch die Assoziationen (und Aggregationen) finden, in denen die einander gegenüberstehenden Verbindungsrollen aller Objekte einer Klasse in der Assoziation keine Navigierbarkeit erfordern. Inaktivieren Sie in diesen Fällen die Navigierbarkeit für die jeweilige Rolle der Klasse.
  • Wenn die Assoziation selbst Attribute enthält (dargestellt durch Assoziationsklassen), erstellen Sie eine Designklasse, die die Assoziationsklasse mit den entsprechenden Attributen darstellt. Fügen Sie diese Klasse zwischen die beiden anderen Klassen ein und richten Sie Assoziationen mit der entsprechenden Multiplizität zwischen der Assoziationsklasse und den anderen beiden Klassen ein.
  • Geben Sie an, ob Assoziationsenden geordnet sein müssen oder nicht. Dies ist erforderlich, wenn die Objekte, die einem Objekt am anderen Ende der Assoziation zugeordnet sind, eine Reihenfolge haben, die eingehalten werden muss.
  • Wenn die zugeordnete (oder aggregierte) Klasse nur von der aktuellen Klasse referenziert wird, können Sie die Klasse verschachteln. Die Vorteile der Verschachtelung von Klassen sind eine schnellere Nachrichtenübertragung und ein einfacheres Designmodell. Verschachtelte Klassen haben jedoch auch Nachteile. Beispielsweise wird der Speicherplatz für verschachtelte Klassen statisch zugeordnet, selbst wenn es keine Instanzen der verschachtelten Klasse gibt. Außerdem ist keine Unterscheidung von der übergeordneten Klasse anhand einer Objekt-ID und keine Referenz auf verschachtelte Klasseninstanzen außerhalb der übergeordneten Klasse möglich.

Assoziationen und Aggregationen lassen sich am besten in einem Klassendiagramm definieren, das die zugeordneten Klassen veranschaulicht. Eigner des Klassendiagramms muss das Paket sein, das die zugeordneten Klassen enthält. Abbildung 4 zeigt ein Beispiel für ein Klassendiagramm, das Assoziationen und Aggregationen veranschaulicht.

Im Begleittext beschriebenes Diagramm

Abbildung 4: Beispiel für ein Klassendiagram mit Assoziationen, Aggregationen und Generalisierungen zwischen Klassen

Subskriptionsassoziationen zwischen Analyseklassen behandeln

Subskriptionsassoziationen zwischen Analyseklassen werden verwendet, um Ereignisabhängigkeiten zwischen Klassen zu identifizieren. Im Designmodell müssen Sie diese Ereignisabhängigkeiten explizit behandeln, entweder mit verfügbaren Ereignis-Handler-Frameworks oder durch Entwerfen und Erstellen eines eigenen Ereignis-Handler-Framework. In einigen Programmiersprachen wie Visual Basic ist dies relativ einfach: Sie deklarieren und behandeln die entsprechenden Ereignisse. In anderen Sprachen müssen Sie unter Umständen zusätzliche Bibliotheken mit wiederverwendbaren Funktionen für die Behandlung von Subskriptionen und Ereignissen verwenden. Wenn die Funktionalität nicht gekauft werden kann, muss sie entworfen und erstellt werden. Weitere Informationen hierzu finden Sie im Abschnitt Technik: Subskriptionsassoziation.

Interne Struktur definieren

Einige Klassen stellen komplexe Abstraktionen dar und haben eine komplexe Struktur. Bei der Modellierung einer Klasse möchte der Designer vielleicht die internen teilnehmenden Elemente und ihre Beziehungen darstellen, um sicherzustellen, dass der Implementierer die Kollaborationen innerhalb dieser Klasse entsprechend implementiert.

In UML 2.0 sind Klassen als strukturierte Klassen definiert, die eine interne Struktur und Ports haben können. Klassen können in verbundene Teile zerlegt werden, die dann noch weiter zerlegt werden können. Eine Klasse kann gekapselt werden, indem eingehende Kommunikation über Ports geleitet werden, die deklarierten Schnittstellen entsprechen.

Wenn Sie eine komplexe Klasse mit komplexer Struktur finden, erstellen Sie ein zusammengesetztes Strukturdiagramm für diese Klasse. Modellieren Sie die Teile, die die Rollen für dieses Klassenverhalten übernehmen. Definieren Sie mit Hilfe von Konnektoren, wie die Teile miteinander verbunden werden. Verwenden Sie Ports mit deklarierten Schnittstellen, wenn Sie zulassen möchten, dass unterschiedliche Clients dieser Klasse auf bestimmte Teile des Verhaltens dieser Klasse zugreifen. Verwenden Sie Ports auch, um die internen Teile dieser Klasse von der Umgebung zu isolieren.

Weitere Informationen zu diesem Thema und Beispiele zu zusammengesetzten Strukturdiagrammen finden Sie in Konzept: Strukturierte Klasse.

Generalisierungen definieren

Klassen können in einer Generalisierungshierarchie organisiert werden, um allgemeines Verhalten und allgemeine Strukturen zu verdeutlichen. Es kann eine allgemeine Superklasse definiert werden, von der Unterklassen Verhalten und Struktur erben können. Generalisierung ist eine Notationserleichterung, die Ihnen ermöglicht, allgemeine Strukturen und allgemeines Verhalten zentral zu definieren und an den Stellen wiederzuverwenden, an denen dieses Verhalten und diese Strukturen wieder auftreten. Weitere Informationen zu Generalisierungsbeziehungen finden Sie im Abschnitt Technik: Generalisierung.

Wenn Sie eine Generalisierung finden, erstellen Sie eine allgemeine Superklasse, die die allgemeinen Attribute, Assoziationen, Aggregationen und Operationen enthält. Entfernen Sie das allgemeine Verhalten aus den Klassen, die zu Unterklassen der allgemeinen Superklasse werden. Definieren Sie eine Generalisierungsbeziehung von der Unterklasse zur Superklasse.

Anwendungsfallkollisionen auflösen

Dieser Schritt verhindert Parallelitätskonflikte, die auftreten könnten, wenn zwei oder mehr Anwendungsfälle gleichzeitig und unter Umständen in unterschiedlicher Weise auf Instanzen der Designklasse zugreifen.

Wenn Sie den Designprozess Anwendungsfall für Anwendungsfall durcharbeiten, besteht eine Schwierigkeit darin, dass zwei oder mehr Anwendungsfälle versuchen könnten, Operationen in Designobjekten gleichzeitig, aber widersprüchlich aufzurufen. In diesen Fällen müssen Parallelitätskonflikte explizit identifiziert und gelöst werden.

Wenn Sie mit synchroner Nachrichtenübertragung arbeiten, werden bei der Ausführung einer Operation nachfolgende Aufrufe der Objekt so lange blockiert, bis die Operation abgeschlossen ist. Eine synchrone Nachrichtenübertragung impliziert das FIFO-Prinzip bei der Nachrichtenverarbeitung. Dies kann den Parallelitätskonflikt lösen, insbesondere in den Fällen, in denen alle Nachrichten dieselbe Priorität haben oder alle Nachrichten in demselben Ausführungs-Thread ausgeführt werden. Wenn ein Objekt von unterschiedlichen Ausführungs-Threads (dargestellt durch aktive Klassen) aufgerufen werden kann, müssen explizite Mechanismen verwendet werden, um den Parallelitätskonflikt zu verhindern bzw. zu lösen.

In Echtzeitsystemen, in denen Threads durch Kapseln dargestellt werden, muss das Problem mehrerer gleichzeitiger Zugriffe auf passive Objekte gelöst werden, wobei die Kapseln selbst einen Warteschlangenmechanismus unterstützen und eine umfassende Semantik für die Behandlung gleichzeitiger Zugriffe einsetzen. Es empfiehlt sich, passive Objekte in Kapseln einzubinden. Dadurch wird die Problematik gleichzeitiger Zugriffe durch die Semantik der Kapsel selbst vermieden.

Es ist möglich, dass mehrere Operationen in demselben Objekt gleichzeitig von unterschiedlichen Ausführungs-Threads aufgerufen werden, ohne einen Parallelitätskonflikt zu erzeugen. Beispielsweise könnten Name und Adresse eines Kunden gleichzeitig geändert werden, ohne dass ein Konflikt entsteht. Nur wenn unterschiedliche Ausführungs-Threads versuchen, dieselbe Eigenschaft des Objekts zu ändern, tritt ein Konflikt auf.

Identifizieren Sie für jedes Objekt, das gleichzeitig von unterschiedlichen Ausführungs-Threads aufgerufen werden kann, die Codeabschnitte, die vor simultanem Zugriff geschützt werden müssen. In einem frühen Stadium der Ausarbeitungsphase ist die Identifizierung spezifischer Codesegmente unmöglich. In diesem Fall müssen die zu schützenden Operationen ausreichen. Bestimmen oder entwerfen Sie anschließend die entsprechenden Steuerungsmechanismen, um konfliktträchtige simultane Zugriffe zu verhindern. Beispiele für solche Mechanismen sind die Steuerung von Nachrichtenwarteschlangen für serialisierte Zugriffe, die Verwendung von Semaphoren oder Token, um jeweils nur einem Thread den Zugriff zu erlauben, und andere Varianten von Sperrmechanismen. Die Auswahl des Mechanismus ist in der Regel implementierungsabhängig und variiert gewöhnlich mit der Programmiersprache und der Betriebsumgebung. Anleitungen zur Auswahl von Parallelitätsmechanismen finden Sie in den projektspezifischen Richtlinien.

Behandlung nicht funktionaler Anforderungen im Allgemeinen

Die Designklassen werden präzisiert, um allgemeine, nicht funktionale Anforderungen zu behandeln. Eine wichtige Vorgabe für diesen Schritt sind die nicht funktionalen Anforderungen an eine Analyseklasse, die möglicherweise bereits in den Sonderanforderungen und Zuständigkeiten der Analyseklasse beschrieben sind. Solche Anforderungen werden häufig mit Hilfe der Architekturmechanismen (Analysemechanismen) beschrieben, die erforderlich sind, um die Klasse zu realisieren. In diesem Schritt erfolgt dann die Präzisierung der Klasse und das Einbinden der Designmechanismen für diese Analysemechanismen.

Die verfügbaren Designmechanismen werden vom Softwarearchitekten identifiziert und charakterisiert. Beschreiben Sie für jeden erforderlichen Designmechanismus so viele Merkmale wie möglich und geben Sie gegebenenfalls Bereiche an. Weitere Informationen zu Designmechanismen finden Sie in Aufgabe: Designmechanismen identifizieren, Konzept: Analysemechanismen und Konzept: Design- und Implementierungsmechanismen.

Beim Design von Klassen müssen unter Umständen mehrere allgemeine Designrichtlinien und -mechanismen berücksichtigt werden. Beispiele:

  • Verwendung vorhandener Produkte und Komponenten
  • Anpassung an die Programmiersprache
  • Verteilung von Objekten
  • Angemessene Leistung
  • Bestimmte Sicherheitsstufen
  • Fehlerbehandlung
Ergebnisse auswerten

In diesem Stadium müssen Sie das Designmodell prüfen, um sicherzustellen, dass Sie mit der Arbeit auf dem richtigen Weg sind. Es ist nicht erforderlich, das Modell im Detail zu prüfen, aber Sie sollten sich die folgenden Prüflisten ansehen:



Weitere Informationen