Programmierung mit dem Klassendiagramm

Mit objektorientierten Programmiersprachen hat der Entwickler mächtige Sprachmittel, um komplexe Systeme realisieren zu können. C++ ist eine weit verbreitete objektorientierte Programmiersprache. Als Visualisierungsmittel objektorientierter Programme gilt die international standardisierte Beschreibungssprache UML (Unified Modeling Language). SiSy bietet dem Entwickler das UML-Klassendiagramm mit Codegenerierung für unterschiedliche Plattformen, unter anderem für die WIN32 API aber auch für AVR- und ARM-Mikrocontroller. Der folgende Abschnitt beschreibt die Grundelemente des Klassendiagramms in SiSy. Sollten Sie mit der UML vertraut sein, können Sie diesen Abschnitt gern überspringen.

Die folgende Abbildung zeigt Ihnen eine Kurzübersicht der Modellierungselemente des UML-Klassendiagramms.

Darstellung von Attributen:
Attribute beginnen mit einem Kleinbuchstaben.

Sichtbarkeit name : Typ = Initialwert {Merkmal}
# temperatur : uint8_t = 25

Schreibweise von Operationen:
Operationen beginnen mit einem Kleinbuchstaben.

Sichtbarkeit name (Parameter:Typ = Standardwert, ...) : Rückgabetyp {Merkmal}
+ setTemperatur ( temp : integer = 25 ) : bool

Um den Aufbau (die Struktur) eines Anwendungsprogramms zu beschreiben nutzen wir in der UML das Klassendiagramm. Bei der Konstruktion einer Anwendung stellt sich immer wieder die Frage nach dem „Erfinden“ von Klassen. Es sei daran erinnert, dass unsere Sprache objektorientiert ist. Wir geben den Dingen Namen! Wenn wir es in unserer natürlchen Sprache beschreiben können, ist es auch möglich, dies als Klassendiagramm zu konstruieren. Eine Klasse ist aus Sicht des Programmdesigners zunächst der Name für etwas im zukünftigen Programm. Für den Programmierer ist eine Klasse der Name und die Beschreibung der Attribute und Operationen eines bestimmten Typs von Systembausteinen. Die UML fordert, eine Klasse als Rechteck darzustellen. Der Name der Klasse soll mit einem Großbuchstaben beginnen.

Jedes Fenster muss erst einmal initialisiert werden bevor es angezeigt werden kann. Das ist eine Operation, die das Fenster selbst ausführen soll. Diese Operation wird in der UML der Klasse zugeordnet. Operationen erscheinen als Liste im Klassenrahmen.

Objekte sind in der Programmierung Instanzen von Klassen. In der UML werden Objekte ebenfalls als Rechteck dargestellt. Die Kennzeichnung als Instanz erfolgt durch Unterstreichen des Namens. Zusätzlich kann der Typ der Instanz angezeigt werden. Die Instanzbeziehung zwischen einem Objekt und seiner Klasse wird als Abhängigkeit (gestrichelte Linie mit offenem Pfeil) und dem Stereotyp «instanceOf» dargestellt.

In der gezeigten UML-Darstellung wurde Folgendes festgelegt:

  • Es gibt eine Klasse Fenster.
  • Die Klasse Fenster verfügt über die Operation onInitWindow.
  • Es gibt ein globales Objekt mit dem Namen meinFenster vom Typ Fenster.
  • Das Objekt meinFenster ist eine Instanz der Klasse Fenster.

Klassen können Eigenschaften (Attribute und Operationen) von anderen Klassen erben. Dies ermöglicht die elegante Wiederverwendung von einmal geschriebenem Code. Klassenbibliotheken und die darin enthaltenen Basisklassen beschleunigen die Entwicklungsarbeit enorm. Bei der Vererbung kann man auch je nach Lesart von einer Generalisierung (von unten nach oben gelesen) oder einer Spezialisierung (von oben nach unten gelesen) sprechen. Eine Generalisierung wird in der UML als Voll-Linie mit einem großen nicht ausgemalten Pfeil zur Basisklasse dargestellt. Die Eselsbrücke für die korrekte Richtung des Pfeils lautet „ist ein“.

In der gezeigten UML-Darstellung wurde Folgendes festgelegt:

  • Die Klasse Fenster verfügt über die Operation onInitWindow.
  • Das Fenster „ist ein“ SFrameWindow
  • Es wird zugesichert, dass die Basisklasse SFrameWindow aus einem anderen Paket stammt.
  • Es gibt ein globales Objekt mit dem Namen meinFenster vom Typ Fenster.
  • Das Objekt meinFenster ist eine Instanz der Klasse Fenster.

Die UML kennt ein zweites Ausdrucksmittel für den Sachverhalt „ist ein“. Es gibt zahlreiche Anwendungsfälle, bei denen Vorlagen, Muster verwendet oder Vorschriften eingehalten werden sollen. So etwas können Struktur- oder Verhaltensmuster, aber auch Schnittstellendefinitionen sein. Da es sich hierbei nicht um eine Vererbung im eigentlichen Sinne handelt, wird zwar derselbe Pfeiltyp verwendet, aber die Linie wird gestrichelt dargestellt. In der UML spricht man von einer Realisierung.

In der gezeigten UML-Darstellung wurde Folgendes festgelegt:

  • Die Klasse SPointList verfügt über die Operationen addPoint und fromString.
  • Die SPointList ist die Realisierung einer STemplateList

Zum Thema Templates wird es ein eigenständiges Kapitel in diesem Tutorial geben.

Objektorientierte Programmiersprachen kennen verschiedene Konzepte, um die Stabilität von Anwendungen sicherzustellen. Eines der Konzepte ist die Kapselung. Dabei ist es möglich, Elementen, z. B. Attributen und Operationen von Klassen, sogenannte Sichtbarkeiten zuzuordnen. Damit kann verhindert werden, das geschützte Elemente unberechtigt benutzt werden. Die meisten Programmiersprachen unterstützen dies durch entsprechende Schlüsselworte wie public, protected und privat. Die UML bietet Symbole, welche zwischen den Sichtbarkeiten + public, ~ package, # protected und - privat unterscheiden. Die Sichtbarkeit wird bei Operationen und Attributen dem Namen vorangestellt.

In der gezeigten UML-Darstellung wurde Folgendes festgelegt:

  • Die Klasse Fenster verfügt über die Operationen onInitWindow und getValue sowie über das Attribut value.
  • Das Attribut value ist privat und kann nur in Instanzen dieser Klasse benutzt werden.
  • Die Operation getValue ist öffentlich und kann von außen aufgerufen werden.
  • Die Operation onInitWindow ist geschützt und kann nur aus der Klasse und deren Ableitungen heraus aufgerufen werden.
  • Es wird zugesichert, dass die Basisklasse SFramewindow aus einem Paket stammt.
  • Es gibt ein globales Objekt mit dem Namen meinFenster vom Typ Fenster.
  • Das Objekt meinFenster ist eine Instanz der Klasse Fenster.

Zusätzlich sind in dieser Darstellung die Rückgabetypen der Operationen auf void bzw. int festgelegt worden. Dieses UML-Klassendiagramm kann jetzt in Quellcode überführt werden. Dieser kann, hier als vereinfachter Ausschnitt, so aussehen:

// SiSy UML C++ Codegenerator ////////////////////////////////////////////////
class Fenster : public SFrameWindow
{
   public:
	// automatisch generierter Konstruktor	
	Fenster();
	// automatisch generierter Destruktor
	virtual ~Fenster();
	int getValue();
 
   private:
	int value;
 
   protected:
	void onInitWindow();
 
};
...
// globales Objekt
Fenster meinFenster;
//////////////////////////////////////////////////////////////////////////////

Der Stand bis an diese Stelle entspricht dem Grundgerüst eines UML-Projektes in SiSy. Das Werkzeug kümmert sich dabei um solche Details wie includes, defines, etc. Der Entwickler arbeitet im Klassendiagramm. Den generierten Quellcode muss er sich eigentlich nicht mehr als Ganzes anschauen. Das UML-Projekt ist sein „Source“, die generierten Quellcodedateien nur noch temporäre Zwischenprodukte beim Bilden. Übrigens stellt Ihnen das Werkzeug SiSy derartige Grundgerüste an den entsprechenden Punkten der Projekterarbeitung zur Auswahl. Diese muss der Entwickler sich nicht jedes mal neu aufbauen.

Systeme bestehen aus Komponenten, die Komponenten aus Bausteinen, diese wiederum aus Einzelteilen usw. Diese Ganz-Teil-Struktur lässt sich in der UML als Aggregation bzw. Komposition abbilden. Dabei wird durch den oben angesprochenen Codegenerator solch eine Aggregation als Attribut im Code abgebildet.

In der gezeigten UML-Darstellung wurde Folgendes festgelegt:

  • Die Klasse Fenster verfügt über die Operationen onInitWindow und getValue sowie über das Attribut value.
  • Das Attribut value ist privat und kann nur in Instanzen dieser Klasse benutzt werden.
  • Die Operation getValue ist öffentlich und kann von außen aufgerufen werden.
  • Die Operation onInitWindoiw ist geschützt und kann nur aus der Klasse und deren Ableitungen heraus aufgerufen werden.
  • Es wird zugesichert, dass die Basisklasse SFramewindow aus einem Paket stammt.
  • Es gibt ein globales Objekt mit dem Namen meinFenster vom Typ Fenster.
  • Das Objekt meinFenster ist eine Instanz der Klasse Fenster.
  • Die Klasse Fenster besitzt eine SPointList
    • Die Klasse SPointList wird unter dem Namen punkt in der Klasse Fenster als öffentliches Attribut aggregiert.
// SiSy UML C++ Codegenerator ////////////////////////////////////////////////
class Fenster : public SFrameWindow
{
   public:
	// automatisch generierter Konstruktor	
	Fenster();
	// automatisch generierter Destruktor
	virtual ~Fenster();
	int getValue();
        // Aggregation  
	SPointList punkt;
 
   private:
	int value;
 
   protected:
	void onInitWindow();
 
};
...
// globales Objekt
Fenster meinFenster;
//////////////////////////////////////////////////////////////////////////////

Die Aggregation entspricht also einem Attribut der Klasse. Somit ist die folgende UML-Darstellung letztlich genau dasselbe. Die Attributdarstellung spart Platz, ist aber weniger übersichtlich was die Systemarchitektur betrifft.

Sequenzdiagramme dokumentieren ausgewählte Verhaltensweisen eines Systems. In SiSy werden Sequenzdiagramme für jede Operation automatisch generiert, wenn diese ausgewählt ist. Der Entwickler kann anhand des dynamisch erzeugten Sequenzdiagrammes visuell überprüfen, ob die gewünschte Steuerstruktur von ihm erstellt wurde. Dieser Perspektivwechsel zwischen Quellcodezeilen und grafischer Darstellung der Codesequenz kann enorm dazu beitragen, die Qualität des Codes zu verbessern.

void MainWnd::onInitWindow()
{
	// Controls erzeugen
	btnEnde.createCtrl(this,"Ende",-100,-30);
	tacho.createCtrl(this,"Tacho",10,10,100,100);
	tacho.setPos(30);
	tacho.setRange(0,255);
}

Das von SiSy automatisch erstellte Sequenzdiagramm zu diesem Code sieht dann so aus:

Polymorphie ist für Einsteiger oft ein recht schwer verständliches Thema. Dabei ist das Schwierige eher die Erstellung der polymorphen Basisklassen nicht jedoch die Anwendung der Polymorphie durch den Entwickler. Überlassen wir das Bauen der Klassenbibliothek mit Templates und polymorphen Basisklassen den Profis und wenden die uns vorliegenden Bibliotheken einfach an. Die folgende vereinfachte Darstellung der Klassen aus dem Paket SVL_CONTROLS zeigt, dass die einzelnen Bausteine (Controls) einer grafischen Benutzeroberfläche (GUI) in einer Vererbungshierachie angeordnet sind. Die Basisklasse SWindow verfügt über eine virtuelle Operation onPaint (kursiv).

Immer dann, wenn wir virtuelle Operationen einer Basisklasse überschreiben, also eine eigene Operation mit gleicher Signatur erstellen, geben wir unserer Klasse ein individuelles Verhalten auf eine übergeordnete, allen Mitgliedern der Basisklasse gemeinsamen Nachricht. Nichts anderes ist Polymorphie.

Die Klassen SImageControl, SProgressCtrl und SChartControl sind allesamt vom Typ SWindow, verfügen aber über eigene Operationen onPaint. Diese haben also die Operation der Basisklasse SWindow überschrieben. Daraus lässt sich, ohne die Operationen im Detail zu betrachten, schlussfolgern, dass diese Controls ein eigenes, individuelles Verhalten beim Zeichnen (Paint) mitbringen, welches sich von dem anderer Fenster unterscheidet. Wenn das Betriebssystem die einzelnen Fenster auffordert sich zu zeichnen (onPaint-Message) werden diese drei Klassen ein abweichendes, individuelles Zeichnen ausführen.

Den Umstand, das die Klasse SRadioGroup sich direkt um eine Menge von SRadioButton kümmert, wird im Klassendiagramm mit einer Assoziation dokumentiert. Diese drückt aus, dass die Klasse SRadioGroup die von ihr kontrollierten SRadioButton kennt.

BILD

Das Besondere an der Assoziation ist, dass dabei die Instanz der assoziierten Klasse nicht Bestandteil (by value) der Klasse ist, sondern nur auf den „Standort“ der Klasse verwiesen wird (by reference). In C++ wird das als Zeiger abgebildet.

Nächstes Thema