zurück zur Homepage


UNDER CONSTRUCTION

Informatik 10.Klasse

Objektorientierte Modellierung - Automaten und Zustandsdiagramme

In dieser Klassenstufe sind aus der 9.Klasse die Grundprinzipien der funktionalen Modellierung und der statischen Datenmodellierung bekannt (siehe auch Klasse 9). Mit Hilfe der Ablaufmodellierung durch Algorithmen, Zustandsdiagrammen und Sequenzdiagrammen ergänzen wir die Modellierungstechniken und führen sie in der objektorientierten Modellierung zusammen. Die objektorientierte Klasse mit ihren Attributen und Methoden dient bei der Umsetzung (Implementierung) in einer konkreten (objektorientierten) Programmiersprache als Konstruktionsplan. Der Lehrplan wird themenzentriert anhand von konkreten Beispielen umgesetzt. Rumbaugh u.a. haben in ihrem Buch Objektorientiertes Modellieren und Entwerfen die Zielsetzungen der Modellierungsarten treffend in folgendem Satz formuliert: "Das funktionale Modell spezifiziert, was geschieht, das ablauforientierte Modell spezifiziert, wann etwas geschieht, und das Objektmodell spezifiziert, in Bezug auf wen oder was etwas geschieht."

Programmiersprache

Unter einer Programmiersprache verstehen wir die Schnittstelle zur Kommunikation zwischen Mensch und Computer. In einer Programmiersprache werden Programme für den Computer erstellt, die es erlauben, den Computer Abläufe durchführen zu lassen. Diese Programme werden üblicherweise in einer höheren (plattformunabhängigen) Programmiersprache geschrieben. Ein Compiler (Übersetzer) übersetzt diese Programme dann in Maschinencode. Es gibt viele verschiedene Ansätze für Programmiersprachen. Man unterteilt die Programmiersprachen meist in funktionale Programmiersprachen wie LISP oder Miranda, logische Programmiersprachen wie PROLOG, imperative Programmiersprachen wie C, Pascal, FORTRAN, MODULA und objektorientierte Programmiersprachen wie Java, Oberon, SmallTalk und C++. Es gibt natürlich auch kombinierte Programmiersprachen wie z.B. die funktional-logische Programmiersprache Babel. Wir beschäftigen uns im Unterricht mit der objektorientierten Programmiersprache Java, die wir in der Entwicklungsumgebung BlueJ betrachten. Die Entscheidung für Java erfolgte aus praktischen Gründen, da sie relativ weit verbreitet ist und die Programmierumgebung BlueJ auch für Anfänger geeignet ist. Vom formalen Konzept betrachtet wäre Oberon sicher die erste Wahl unter den objektorientierten Sprachen.

Syntax (Grammatik) der Sprache Java

Bitte beachtet, dass ich nur den Teil der Sprache Java beschreibe, den wir im Unterricht besprochen haben (und das meist an Beispielen). Mir ist bewusst, dass die Beschreibung der Syntax der Sprache Java formal korrekt in der EBNF-Notation oder mit Hilfe von Syntaxdiagrammen erfolgen sollte, aber eine solche Darstellung würde die meisten Schüler unserer 10. Klassen überfordern.
Bei Java wird zwischen Groß- und Kleinschreibung unterschieden.

Kommentare

Kommentare dienen dem Leser des Quellcodes zum besseren Verständnis des Programms. Sie haben jedoch keinerlei Einfluss auf den Ablauf des Programms. In Java gibt es drei verschiedene Arten von Kommentaren:
Einzeilige Kommentare
werden durch die direkt aufeinanderfolgenden Symbole // eingeleitet, danach folgt ein beliebiger Text. Ein solcher Kommentar endet mit dem Zeilenende.
Blockkommentare
werden mit den direkt aufeinanderfolgenden Symbolen /* eingeleitet und enden mit den Symbolen */. Blockkommentare können nicht geschachtelt werden, d.h. ein Auftreten der Zeichenkette /* innerhalb eines Blockkommentars ist nicht erlaubt.
Dokumentations-Kommentare
werden durch die Zeichenkette /** eingeleitet und mit der Zeichenkette */ beendet. Dokumentationskommentare werden in die Dokumentation des Projekts übernommen (als HTML-Datei). Die Strukturierung der Dokumentation kann man durch HTML-Tags und Marken wie @param und @return beeinflussen.

Schlüsselwörter in Java

Die Programmiersprache Java kennt verschiedene Schlüsselwörter, die an verschiedenen Stellen eines Programms auftreten können. Ihre Schreibweise ist eindeutig festgelegt. Im Unterricht werden wir nur eine Auswahl dieser Schlüsselwörter kennenlernen. Die Schüsselwörter von Java, die wir in der 10. Klasse verwenden, sind fett gedruckt:

abstract assert boolean break byte case catch char class const continue default do double else enum extends final finally float for goto if implements import instanceof int interface long native new package private protected public return short static strictfp super switch synchronized this throw throws transient try void volatile while

Bezeichner, also Namen von Klassen, Methoden oder Variablen, die wir selber definieren, dürfen keine Schlüsselwörter sein, d.h. es ist z.B. nicht zulässig, eine Methode abstract zu nennen.

Klassendefinition in Java

Den Grundaufbau einer einfachen Klassen-Definition stelle ich mit Hilfe des folgenden Beispiels vor.


class TEST    // nach dem Schluesselwort class folgt der Name (Bezeichner) 
                 // der hier definierten Klasse
{
     int zahl;     // Deklaration eines Attributs zahl der Klasse TEST -
                   // man sagt auch, eine Variable zahl vom Typ Integer wird deklariert

     TEST()     // Konstruktor der Klasse TEST - im Konstruktor werden u.a. fuer alle Variablen/Attribute
                // sinnvolle Vorbelegungen/Initialisierungen vorgenommen
     {
	   zahl = 1;       // Dem einzigen Attribut dieser Klasse wird der Wert 1 zugewiesen
     }

     void nachfolger()  // Definition einer Methode mit dem Methodenname (Methodenbezeichner)
                        // nachfolger. 
     {
	  zahl = zahl+1;    // zahl erhaelt als Wert den um 1 erhoehten Wert.
     }
}
Das obige Javaprogramm ist eine Umsetzung (Implementierung) des folgenden erweiterten Klassendiagramms der objektorientierten Modellierung:
TEST
int zahl

TEST()
void nachfolger()

Einzelheiten zu der Java-Klasse TEST:

In der Programmzeile int zahl; steht int hierbei für den Datentyp Integer, also für die Menge der ganzen Zahlen. Die Programmzeile deklariert die Variable (das Attribut) zahl als ganze Zahl, d.h. dieser Variablen dürfen nur ganze Zahlen zugewiesen werden. Beim Übersetzen eines Programms wird eine Symboltabelle (unsichtbar für den Benutzer des Programms) erstellt, in der alle Bezeichner mit ihren Datentypen eingetragen werden. Diese Vorgehensweise dient dazu, Programmfehler aufzudecken.

Die Programmzeile void nachfolger() leitet eine Methodendefinition ein und wird Methodenkopf genannt. Das Schlüsselwort void steht dabei für die Tatsache, dass diese Methode keinen Wert zurückliefert, sondern den Zustand des Objekts, für das diese Methode aufgerufen wird, verändert. Solche Methoden werden auch Setzmethoden oder (bei imperativen Programmiersprachen) Prozeduren genannt.
Anmerkung: Weiß jemand, ob man den Begriff Prozeduren auch bei objektorientierten Sprachen verwendet? (bitte E-Mail an mich)

Bemerkenswert ist die Programmzeile zahl = zahl +1; : hier handelt es sich natürlich nicht um eine mathematische Gleichung, sondern um eine Anweisung. Zuerst wird die rechte Seite dieser Anweisung ausgewertet, indem der für die Variable zahl hinterlegte Wert ermittelt wird. Zu diesem Wert wird 1 addiert und dieser neue Wert wird für das Attribut zahl eingetragen. Bei Anweisungen müssen die Datentypen der Ausdrücke beider Seiten übereinstimmen (oder zumindest kompatibel sein), sonst endet eine Übersetzung des Programms mit einem Kompilierungsfehler (meist wird eine Meldung wie incompatible types ausgegeben).

Methoden in Java

Man unterscheidet in Java zwischen Methoden, die ein Objekt eines Datentyps zurückgeben (sogenannte Funktionen), und Methoden, die keine Rückgabe liefern. Eine Methode besteht aus einem Methodenkopf und einem Methodenrumpf. Methoden, die einen Rückgabewert liefern, enthalten in ihrem Rumpf eine mit return eingeleitete Anweisung. Im unteren Programmstück stehen zwei Methoden, die Rückgabewerte liefern. Die Methode berechneQuadrat erwartet einen Eingabeparameter vom Datentyp double und liefert einen Rückgabewert vom Datentyp double. Ein korrekter Aufruf dieser Methode wäre zum Beispiel berechneQuadrat(5), als Rückgabewerte w&uurde dieser Aufruf 25 liefern.

Die Methode berechneQuadrat() habe ich auch in diese Seite eingebunden (allerdings als JavaScript, die Syntax ist natürlich anders), dann könnt ihr Euch noch etwas mehr Quadratzahlen berechnen lassen:
Berechne das Quadrat von =
.... 

double berechneQuadrat (double x)
{
	  return x*x;
}

boolean istRechtwinklig(double a, double b, double c) // Methode mit Rückgabewert vom Datentyp boolean
{
	double fehler = 0.00001;	
       if (a != 0 && b != 0 && c!= 0)
       {	
		a2 = berechneQuadrat(a);
		b2 = berechneQuadrat(b);
		c2 = berechneQuadrat(c);

		if ((a2+b2-c2 < fehler) || (a2+c2-b2 < fehler) 
                     || (b2+c2-a2 < fehler))
		{
			return true;
		}
	}

	return false;
}
.....
Erklärung der Funktion istRechtwinklig: Diese Funktion erwartet als Eingabe drei Parameter des Datentyps double (64-Bit-Gleitkommazahlen) und liefert einen Wahrheitswert als Rückgabewert. Mathematisch erklärt prüft die Funktion, ob ein Dreieck, dessen drei Seitenlängen eingegeben werden, rechtwinklig ist. Dazu wird der Satz des Pythagoras benutzt: die Seitenlängen werden quadriert, mit Hilfe einer bedingten Anweisung wird geprüft, ob die drei eingegebenen Zahlen den Satz des Pythagoras erfüllen. In der Gleitpunktarithmetik sollte man beim Abprüfen der Gleichheit sehr vorsichtig sein und lieber wie in dem gezeigten Beispiel mit einem Fehlerbereich arbeiten. Zur Problematik der Gleitpunktarithmethik kann man hier nachlesen. Die return-Anweisung im Rumpf einer Funktion beendet die Abarbeitung dieser Funktion. Sollten in dem oben angegebenen Funktion nach der Anweisung return false; noch weiter Anweisungen folgen, so werden diese nicht mehr ausgefährt. Die Eingabe von negativen Zahlen, die als Seitenlängen semantisch falsch w"aren, wird in dieser Version der Funktion nicht verhindert.

Bedingte Anweisung

Eine bedingte Anweisung wird in Java mit dem Schlüsselwort if eingeleitet, danach folgt eine in runden Klammern stehende Bedingung (das kann eine einzelne Aussage oder eine Verknüpfung von Aussagen sein, siehe Bedingung) und ein mit geschweiften Klammern umschlossener Anweisungsblock, der nur dann ausgeführt wird, wenn die Bedingung gilt bzw. anders formuliert, wenn die Aussage mit wahr bewertet wird. Die Bedingung wird durch runde Klammern eingeschlossen. Ein optionaler else-Teil, eingeleitet durch das Schlüsselwort else und gefolgt von einem mit geschweiften Klammern umschlossenen Anweisungsblock legt fest, welche Anweisungen ausgeführt werden, wenn die Bedingung mit falsch bewertet wurde. Beispiel:

.... 

boolean istPositiv (double x)
{
       if (x > 0)
       {
            return true;
        }
        else
        {
             return false;
        }
}

.....
Die Funktion istPositiv erhält als Parameter eine Gleitkommazahl und überprüft, ob diese Zahl positiv ist. Falls ja, d.h. die Aussage (x>0) wird mit wahr (true) bewertet, dann wird der Wahrheitswert true als Ergebnis des Funktionsaufrufs geliefert. Falls die Aussage mit falsch bewertet wurde, dann wird die return false; Anweisung durchgeführt. Gleichwertig mit obiger Funktionsdefinition wäre die unten folgende Variante istPositiv2, da die Abarbeitung der Funktion nach der return-Anweisung beendet wird, d.h. die Anweisung return false wird nur dann erreicht, wenn die Aussage (x>0) falsch ist.
.... 

boolean istPositiv2 (double x)
{
       if (x > 0)
       {
            return true;
        }
        return false;
}

.....

Bedingung

Eine Bedingung ist ein Ausdruck, der als Ergebnis einen Wahrheitswert liefert, also true oder false. Die logischen Operatoren || für das logische ODER, && für das logische UND und ,tt>! für die logische Verneinung dienen zur Verknüpfung (|| und &&) bzw zur Verneinung von Aussagen. Ein Methodenaufruf, der als Ergebnis einen Wahrheitswert liefert, ist ebenfalls eine Bedingung. Für Interessierte: Mit Hilfe der de'Morganschen Regeln können logische Ausdrücke vereinfacht werden.

Wiederholung mit Anfangsbedingung

Die Wiederholung mit Anfangsbedingung (salopp oft while-Schleife genannt) wird durch das Schlüsselwort while eingeleitet. Nach diesem Schlüsselwort folgt eine in runden Klammern stehende Bedingung, anschließend folgt ein Anweisungsblock, der wie üblich in geschweiften Klammern steht.
Syntax: while (bedingung) { anweisungen}
Beispiel:
.... 

void Ausgabe_Quadratzahlen (int x)
{
       int zaehler = 1;

       while (zaehler <= x)
       {
            System.out.println("Quadrat von " + zaehler + " ergibt " + zaehler*zaehler);
            zaehler = zaehler +1;
        }
}

.....
Obige Methode gibt die Quadratzahlen von 1 bis zur als Parameter übergebenen Zahl aus. Die Variable zaehler ist dabei eine lokale Variable der Methode, das hei"st sie ist nur in dieser Methode definiert und dient als Hilfsvariable. Da die Quadratzahlen von 1 beginnend ausgegeben werden sollen, wird die Variable zaehler auf den Wert 1 gesetzt. In der Wiederholung mit Anfangsbedingung wird in der Bedingung zaehler <= x geprüft, ob wir noch weitere Quadratzahlen ausgeben müssen, ob also der aktuelle Wert von zaehler kleiner oder gleich dem Wert x ist. Solange diese Bedingung wahr ist, wird der Anweisungsteil zwischen den geschweiften Klammern wiederholt. Natürlich muss im Anweisungsteil die Variable zaehler um eins erhöht werden, um die Quadratzahlen nacheinander auszugeben. Sobald zaehler den Wert x erreicht hat, wird ein letztes Mal der Anweisungsteil ausgeführt, danach ist die Bedingung falsch und die Ausführung der Methode ist beendet.
Sollte als Parameter ein negativer Wert übergeben werden, ist die Bedingung zaehler<=x schon zu Beginn falsch und die Ausführung der Methode ist beendet.
Für Interessierte: Statt der Anweisung zaehler = zaehler +1; kann alternativ die Anweisung zaehler++; verwendet werden.

Die Datenstruktur FELD

FELD ist eine spezielle Klasse, deren Objekte mehrere Werte des gleichen Typs zu einer Einheit zusammenfassen. Ein Feld kann so die enthält-Beziehung der Kardinalität 1:n realisieren. Die einzelnen Feldelemente sind durchnummeriert; in vielen Programmiersprachen wie zum Beispiel in Java beginnt die Nummerierung bei 0. Solche Nummern nennt man (Feld-)Index.

Das hört sich natürlich sehr theoretisch an, deswegen schauen wir uns ein Feld jetzt an einem Beispiel an. Nehmen wir an, wir wollen unsere Meerschweinchen verwalten. Wir haben schon gelernt, dass wir ein Meerschweinchen als Objekt der Klasse MEERSCHWEINCHEN modellieren können (siehe Modellierung). Wir ergänzen die Attribute noch um ein Attribut gewicht, damit wir für unser Beispiel ein paar schöne Methoden zeigen können. Die objektorientierte Klasse MEERSCHWEINCHEN lässt sich im erweiterten Klassendiagramm wie folgt darstellen:
MEERSCHWEINCHEN
String name
String geburtsdatum
String rasse
String farbe
int gewicht

MEERSCHWEINCHEN()
void Zeichne()
String GibName()
void SetzeGewicht(int gewichtNeu)
Da wir in Anlehnung an unser Buch vereinbart haben, dass Attributnamen (Variablen, Parameter) mit einem Kleinbuchstaben und Methodennamen mit einem Großbuchstaben beginnen, halten wir uns hier natürlich auch daran. Um nun alle unsere Meerschweinchen verwalten zu können, brauchen wir eine Klasse HERDE, die mit mehreren Meerschweinchen umgehen kann. Diese Klasse steht in einer 1:n-Beziehung zu unserer Klasse MEERSCHWEINCHEN. Sie soll Methoden zur Verfügung stellen, mit denen wir ein Meerschweinchen eintragen oder löschen können (falls eines geboren wird oder stirbt), mit denen wir alle Meerschweinchen darstellen (zeichnen) lassen können, mit denen wir für ein Meerschweinchen das neue Gewicht eintragen können und ähnliches.
Die 1:n-Beziehung wird durch ein Referenzattribut realisiert, das vom Datentyp Feld von MEERSCHWEINCHEN ist. In der Java-Deklaration müssen wir hier
MEERSCHWEINCHEN [] herde;
schreiben. Damit legen wir fest, dass unser Attribut
herde
ein Objekt der Klasse FELD (das wird durch die eckigen Klammern ausgedrückt) ist, dessen Feldelemente Objekte der Klasse MEERSCHWEINCHEN sein werden. Damit könnte unser Klassendiagramm für HERDE wie folgt aussehen:
HERDE
MEERSCHWEINCHEN [] herde

HERDE()
void ZeichneAlle()
void TrageEin(String name, String geburtsdatum, int gewicht, String rasse, String farbe)
void Loesche(String name)
void SetzeGewicht(String name, int gewichtNeu)
int GibAnzahl()
Damit ist der Grundaufbau dieser Java-Klasse schon eindeutig festgelegt:

class HERDE    // nach dem Schluesselwort class folgt der Name (Bezeichner) 
                 // der hier definierten Klasse
{
     MEERSCHWEINCHEN [] herde;     // Deklaration des Attributs herde als Objekt eines Feldes, 
                   // das Objekte der Klasse MEERSCHWEINCHEN verwalten kann

     HERDE()     // Konstruktor der Klasse HERDE
     {
	   herde = new MEERSCHWEINCHEN[50];       // Erzeugen eines Feldes mit 50 Feldelementen; damit
                                                  // können maximal 50 Meerschweinchen verwaltet werden
     }

     void ZeichneAlle()
     {
	 ....
     }

     void TrageEin(String name, String geburtsdatum, int gewicht, String rasse, String farbe)
     {
           .....
     }

     void Loesche(String name)
     {
           .....
     }

     void SetzeGewicht(String name, int gewichtNeu)
     {
           ....
     }

     int GibAnzahl()
     {
           ....
     }
}

Fortsetzung folgt!

Ausgabemethode System.out.println

Die Methode System.out.println ist eine Standardfunktion in Java und muss nicht speziell importiert werden. Sie gibt die Zeichenkette, die sie als Parameter bekommt in ein Konsolenfenster aus. Java verfolgt die call-by-value Auswertungsstrategie bei Parametern, wertet also zuerst den Ausdruck in der Klammer aus. Als Parameter ist deswegen eine in doppelte Hochkommata geklammerte Aneinanderreihung von beliebigen Zeichen möglich und eine Verkettung von Zeichenketten mit Hilfe des "+"-Operators. Wenn eine Zeichenkette mit dem "+"-Operator mit einer Variablen (ohne Hochkommata!) verkettet wird, deren Datentyp eine Zahl ist, dann wird die Zeichenkette mit der textuellen Ausgabe der entsprechenden Zahl verkettet.

1. Beispiel: Die Methodenaufrufe System.out.println("Das ist ein Test"); und System.out.println("Das " + "ist " + "ein " + "Test"); liefern die selbe Ausgabe in dem Konsolenfenster (bei der zweiten Version die Blanks/Leerzeichen nicht vergessen!).
2. Beispiel: x sei als Integervariable definiert und habe aktuell den Wert 102. Der Methodenaufruf System.out.println("Wert von x: " + x )liefert bei seiner Auswertung im Konsolenfenster den Text Wert von x: 102

Bezeichner

Die Namen, die der Programmierer seinen Methoden, Attributen und Variablen gibt, nennt man Bezeichner (identifier). Ein Bezeichner muss mit einem Buchstaben beginnen, dann kann eine beliebige Aneinanderreihung von Buchstaben, Ziffern und Unterstrichen folgen. Schlüsselwörter können nicht als Bezeichner verwendet werden. Auch die drei konstanten Werte (Literale) true, false und null dürfen nicht als Bezeichner verwendet werden. Mögliche Bezeichner wären z.B. zaehler, v_x, zufall_1, ...

Tipps zum Programmierstil:
Aus Rücksicht auf andere Sprachkreise sollten Umlaute, "ß" und andere Exoten nicht in einem Bezeichner vorkommen. Bezeichner sollten sinnvoll gewählt werden, das erhöht die Lesbarkeit eines Quellcodes deutlich. Die Namen vordefinierter Klassen wie z.B. Object, String, System sollten ebenfalls nicht als Bezeichner gewählt werden.


Letzte Änderung: 14.04.2014, Autor: A. Wedel