Den Begriff der Klasse kennen viele sicher von dem objektorientierten Programmieren. Doch was genau ist eine Klasse, was sollte beachtet werden, wenn eine Klasse definiert wird, was sind die Anwendungsmöglichkeiten, wie lauten die Fachbegriffe und wie sind die Diagramme zur Modellierung aufgebaut?

Definition

„Eine Klasse fasst in abstrakter Form viele ähnliche Objekte zusammen und beschreibt dabei deren Struktur, Verhalten und Beziehungen.“
Das Ganze können wir auch ein bisschen praktischer formulieren. Wird etwas programmiert, so hat es einen Aufgabenbereich, der aus vielen Objekten besteht. Wenn wir ein Zimmer betrachten, wären die Objekte in diesem Zimmer: Tische, Stühle, Regale, ein Bett, ein Sofa und anderes Mobiliar. Um nicht alles einzeln zu definieren zu müssen, fassen wir gleichartige Eigenschaften und Verhalten (Höhe, Breite, Tiefe, Preis …) in der Klasse „Mobiliar“ zusammen. Jetzt muss nicht jedes Objekt ganz neu definieren werden, sondern nur die Klasse auf die Ausprägungen jedes Objekt ein bisschen genauer angepasst werden. Wenn die Aufgabenstellung komplizierter ist, so sollte das Problem in viele kleinere Klassen zerteilt werden. Daher sind gute Synonyme für das Wort Klasse die Begriffe Vorlage, Blaupause oder Stanzform für Objekte.
Objekte können sich während der Laufzeit in ihrem vorgegebenen Rahmen der Klasse frei bewegen, weshalb ihr Verhalten als dynamisch beschrieben wird. Klassen dagegen sind während der Laufzeit unveränderbar in ihrem Verhalten und werden daher statisch genannt. Das führt dazu, dass Objekte immer ihre Klasse kennen, während die Klasse nicht zwanghaft ihre Objekte kennen muss.

Attribute

Attribute einer Klasse, beschreiben die Werte, die als Objekt dieser Klasse vorkommen werden. So haben die Objekte der Klasse zwar alle die gleichen Attribute, aber nicht zwingend die gleichen Werte. In den Klassendiagrammen wird ein Attribut immer klein geschrieben und haben einen Datentyp (meist werden die Datentypen aus der verwendeten Programmiersprache verwendet, da es keine Standardisierung gibt). Außerdem können die Attribute um …

  • Sichtbarkeit (visibility)
    • + für public (der Sichtbarkeit für alle Klassen)
    • – für private (der Sichtbarkeit innerhalb der Klasse)
    • # für protected (der Sichtbarkeit innerhalb der Klasse und aller Unterklassen à Vererbung)
    • ~ für die Sichtbarkeit eines packages),
  • Initialwerte werden in {} angegeben
  • Klassenattribute (Wenn alle Objekte auf ein gemeinsames Attribut zugreifen sollen), werden unterstrichen
  • abgeleitete Attribute (Wenn das Attribut aus bestehenden Attributen berechnet werden kann) werden mit einem / markiert
  • Zusicherungen (constraints) die das Attribut zu jedem Zeitpunkt in einem Bereich zulässiger Bedingungen halten.

erweitert werden.

Methoden

Operationen werden auch Methoden genannt, sie beschreiben ausführbare Tätigkeiten der Klasse. Alle Objekte einer Klasse haben dieselben Operationen, die alle dasselbe Verhalten aufweisen. Alle öffentlichen Methoden werden als Schnittstelle einer Klasse bezeichnet. Die Mindestanforderungen an eine Methode sind ein kleingeschriebenes Verb, welches den Verarbeitungsschritt beschreibt und die Signatur. Für unsere Beispielklasse „Mobiliar“ wäre eine Methode berechnePreis() und gibHoehe() gute Beispiele. Außerdem können die Methoden um…

  • Sichtbarkeit
  • Rückgabewerte
  • Klassenoperationen (benötigen kein Objekt um aufgerufen zu werden)

… erweitert werden. Die Kommunikation zwischen Objekten geschieht ausschließlich über diese Methoden

Erweiterungen

Um weitere Funktionen einer Klasse noch genauer beschreiben zu können, können Klassen um Stereotypen und Eigenschaftswerte erweitert werden.

Stereotypen (Stereotypes) sind besonders wichtig bei Programmen, die viele Klassen enthalten. Um nicht den Überblick zu verlieren werden die Klassen zu Gruppen zusammengefasst. Die Klasse „Mobiliar“ wäre in einer größeren Möbelfabrik mit der Stereotypen „Hauptprodukte“ gekennzeichnet worden. In UML Diagrammen (dem Standard zum Modellieren von Klassendiagrammen) gibt es zudem noch einige vordefinierte Stereotypen, auf die zurückgegriffen werden sollten. Sie werden durch << Stereotype >> über dem Klassennamen gekennzeichnet.

Die Eigenschaftswerte (Property Strings) beschreiben besondere Merkmale von Modellelementen. Sie stehen direkt hinter dem Element in geschweiften Klammern. Für unsere Beispielklasse „Mobiliar“ wäre ein geeigneter Eigenschaftswert {visibility = true}, damit alle Objekte der Klasse sichtbar sind. Weitere Eigenschaftswerte, welche häufig zur Anwendung kommen sind „readOnly“ für Klassen, die nicht bearbeitet werden dürfen, „ordered“ falls die Reihenfolge von Bedeutung ist und „unique“ wenn es nur ein Objekt davon geben darf.

Datenkapselung

Klassen sollten nach dem Prinzip der Datenkapselung designt werden. Das bedeutet, dass ihre Objekte die Werte ihrer Attribute verbergen und nur über Methoden preisgeben und verändern lassen. Die Verarbeitung der Daten bleibt dem Anwender völlig verborgen und interessiert ihn (theoretisch) nicht, da er vertraut, dass die Methoden so funktionieren, wie sie beschrieben wurden. Daher wird die Datenkapselung auch Geheimnisprinzip genannt. Dadurch wird eine geringe Veränderung bei Implementierungsänderungen erzeugt, was einen Dominoeffekt verhindert, die Fehlersuche wird vereinfacht und es kann keine Inkonsistenten Zustände mehr geben. Das Einzige was bedenken macht ist der große Aufwand saubere Schnittstellen zu definieren und die geringere Performance, wenn alles über Methoden aufgerufen wird.

Beispiel

Hier noch eine Skizze unserer Beispielklasse Mobiliar, mit allem was wir gelernt haben.

Das Klassendiagramm unserer Beispielklasse Mobiliar

Class Responsibility Collaboration

Oder kurz CRC ist ein Konzept, bei dem auf einer Karteikarte der Klassenname, die Verantwortlichkeiten der Klasse und alle Klassen, die in Zusammenarbeit stehen aufgefasst werden. Dadurch können die einzelnen Szenarien schnell durchgespielt werden und die Komplexität der einzelnen Klasse verständlich zusammengeführt werden. Dieses Konzept hilft besonders unerfahrenen Entwicklern objektorientiert zu denken ohne sich mit der Implementierung zu befassen, es ist billig und lässt die Anwender miteinbeziehen. Abläufe und häufige Muster in dem System fallen dadurch auf und können so beachtet werden.

Dabei sollten 4 wichtige Punkte beachtet werden:

  1. Jede Klasse hat einen sinnvollen und einzigartigen Namen, der auf den Bereich zugeschnitten ist
  2. Die Verantwortlichkeiten der Klasse passen zusammen
  3. Eine Klasse hat wenig Verantwortlichkeiten
  4. Eine Klasse muss mit wenig anderen Klassen zusammenarbeiten

Klassendiagramme

Wenn die Konzepte für die einzelnen Klassen ausgearbeitet sind, sollten alle Klassen zu einem großen gesamten zusammengeführt werden. Daher werden die Klassen jetzt in Beziehung miteinander gesetzt.
Die einfachste Beziehung ist die Assoziation. Eine Assoziation beschreibt die dauerhafte und strukturelle Beziehung zwischen Klassen. Sie ist bidirektional, was bedeutet, dass es keine Abhängigkeiten (im Sinne von der Existenz der Klasse) zwischen den Klassen gibt. Die Assoziation kann um einen Namen (beschreibendes Verb) mit Leserichtung (Dreieck) erweitert werden. Aus dem Namen und der Leserichtung lassen sich sinnvolle Sätze bilden. In diesem Beispiel wäre es „Klasse 1 unterstützt Klasse 2.“.

Wenn über die Klassen zusätzliche Informationen gegeben werden sollen, hat die UML optionale Rollennamen anzubieten. Rollennamen sind Nomen, die Klassen als Rolle in einer Beziehung beschreiben. Insbesondere Reflexive Beziehungen (Beziehung mit sich selbst) werden dadurch verständlicher. Folgendes Beispiel sollte klären:

Die nächste Verfeinerung unserer Beziehung ist das einführen von Multiplizitäten oder auch Kardinalität genannt. Darüber kann die Anzahl der Objekte festgelegt werden, die aus der Klasse entstehen können. Die üblichen Werte in Diagrammen sind:

  • * für unendlich Objekte
  • 4 für 4 Objekte (beispielhaft einen festen Wert)
  • 1 … * für 1 bis unendlich Objekte
  • 1 … 4 für 1 bis 4 (eine feste Menge an Objekten)

Die Multiplizitäten werden neben das Objekt geschrieben. Beispielhaft sieht das so aus:

Das „orderd“ ist ein Eigenschaftswert, wie er schon in Erweiterungen vorgestellt wurde.

Oft werden Assoziationen in ihrer bidirektionalen Form gar nicht benötigt, sondern es reicht die Beziehung ein eine Richtung zu implementieren. Dadurch wird die eine Klasse Dienstleister für die andere Klasse. Diese gerichteten Beziehungen werden durch eine Peilspitze auf die Dienstleisterklasse gekennzeichnet. Vorsicht nicht mit Rollennamen verwechseln!

Wenn die Beziehung sagen soll, dass es nur ein Teil von einem Ganzen ist (bei Teilen in einer Baugruppe), so sollte eine Aggregation verwendet werden. Aggregationen werden von dem Bestandteil zum Ganzen (Aggregat) gezeichnet und am Ende durch eine weiße Raute gekennzeichnet. Dadurch lassen sich Sätze bilden wie „Räder sind ein Teil von Autos, aber Autos ist NICHT Teile von Rädern.“. Diese Beziehung ist aber trotzdem bidirektional, da beide Klassen auf sich zu navigieren können. Falls sie nicht wissen sollten ob sie eine Aggregation oder Assoziation wählen sollten, beginnt mit einer Assoziation. Sollte über die Klassen aber folgende 2 Eigenschaften gelten, dann ist es sicher eine Aggregation:

  1. Ändert sich die Eigenschaft des Gesamten, so ändern sich auch seine Teile
  2. Nachrichten, die das Ganze betreffen, betreffen auch das Teil

Und hier noch ein paar Beispiele für Aggregationen:

Die nächst engere Beziehung nach der Aggregation ist die Komposition. Alles was schon für die Aggregation gegolten hat, gilt auch für die Komposition. Als Besonderheit ist die Kopplung der Lebensdauer aller Teile an die Existenz des Gesamten (Aggregat). So kommt es zu 4 extra Regeln:

  1. Teile werden mit oder nach dem Aggregat erzeugt
  2. Teile werden vor oder mit dem Aggregat zerstört
  3. Wenn es zu einer automatischen Löschung des Aggregaten kommt, werden auch alle Teile vernichtet
  4. Die Verantwortung kann an andere Aggregate übertragen werden

Durch die bedingte Existenz ist die Multiplizität des Aggregats immer 1 oder 0 … 1 und die Teile können nicht geteilt werden. Bei einer reflexiven Verwendung der Klasse muss eine Seite sogar 0 enthalten, sonst würde sich dadurch eine Endlosschleife ergeben. Dargestellt wird das ganze durch eine gefüllte Raute welche von dem Teil auf den Aggregaten gerichtet ist.

Hier eine bessere Darstellung für das Beispiel des Polygonzuges:

Vererbung

Vererbung beschreibt die Beziehung einer allgemeinen Oberklasse zu einer spezialisierten Unterklasse. Dabei verfügt die Unterklassen über alle Eigenschaften der Oberklasse. Dabei kann von einer oder mehreren Klassen geerbt werden. Da in der Vererbung schon eine Struktur gibt, können hier auf Multiplizitäten, Rollen und Namen der Beziehung verzichtet werden. Die Betonung liegt bei der Vererbung auf Gemeinsamkeit innerhalb der Struktur.
Die Unterklasse erbt dabei Verhalten, Zustand und Beziehungen der Oberklasse, zusätzlich definiert sie eigene Attribute, Methoden und Beziehungen. Außerdem besteht die Möglichkeit, dass die Methoden aus der Oberklasse mit einer eigenen Version überschrieben werden.
Dargestellt werden Vererbungsbeziehungen über einen Pfeil mit einem leeren Dreieck als Pfeilspitze von Unter- zu Oberklasse.

Bei der Einfachvererbung hat jede Unterklasse genau eine Oberklasse, der sie zugeordnet ist. Dabei kennt die Unterklasse die Oberklasse, die Oberklasse aber nicht ihre zugehörigen Unterklassen. Falls es mehrere Unterklassen geben sollte, empfiehlt sich die Darstellung als Baum.

Die Mehrfachvererbung besagt, dass die Unterklasse alle Eigenschaften aller zugehöriger Oberklassen erbt. Das kann zu Problemen führen, wenn in den Oberklassen gleiche Methodennamen oder Attribute verwendet wurden.

Die Anzahl der Vererbungsebenen kann theoretisch unendlich sein, aus Übersichtsgründen sollte sie aber nicht 5 Ebenen überschreiten. Je nach Vorgehensweise können verschiedene Hierarchien entstehen. Bei der Bottem-Up Vorgehensweise ist das Verfahren die Verallgemeinerung (Generalisierung). Wir nehmen alle Einzelfälle und fassen Gleiches zusammen. Der Gegensatz dazu ist die Top-Down Vorgehensweise bei der wir von Allgemeinen uns in spezielle Klassen eindenken. Dabei steht es dem Designer offen ob er Zwischenabstraktionen / Zwischenverallgemeinerungen vornimmt.

Polymorphismus ist dann die Vielgestaltigkeit einer Schnittstelle. Zu einer Methode gehören mehrere unterschiedliche Implementierungen, die während der Laufzeit ausgewählt und ausgeführt werden. Meist sind Polymorphismus und Vererbung in den Sprachen gekoppelt.

Schnittstellen

Wenn eine Klasse nur auf einen ausgewählten Bereich der anderen Klasse zugreifen darf, so wird eine Schnittstellenklasse implementiert. Diese garantiert den teilweisen Zugriff auf diese Klasse. In die Beziehungsverbindung wird ein Kästchen gezeichnet in dem der Schnittstellenname und der Stereotype <<interface>> steht. Auf der Seite, die die Schnittstelle benutzt wird der Stereotype <<use>> geschrieben und die andere hat einen Vererbungsstich mit gestrichelter Linie.:

Da bei Mehrfachvererbung oft Methoden doppelt verwendet werden können über die Verwendung von Schnittstellen das Problem gelöst werden.

Für weitere Informationen über Klassen und UML klicke dich durch die folgenden Links:
UML:
1. https://www.studiumbestehen.de/uml-unified-modeling-language-diagramme-fuer-objektorientierte-softwareentwicklung/
2. https://uml.org/
Klassen:
https://www.studiumbestehen.de/klassen-in-java/