www.r-krell.de
Webangebot für Schule und Unterricht, Software, Fotovoltaik und mehr

Willkommen/Übersicht  >  Informatik  >  

Objektorientierte Anwendungsentwicklung bzw. Softwareengineering

mit UML-Klassendiagramm, UML-Sequenzdiagramm und ereignisgesteuerten Prozessketten (EPK)



Auf dieser Seite finden Sie:


zum Seitenanfang / zum Seitenende

Softwareentwicklung
mit grafischen Veranschaulichungen



Nach dem neuen Lehrplan für unseren Bildungsgang 1, 'Abitur mit Schwerpunkt Mathematik/Informatik' (kurz 'Mathe-Kolleg', oder offiziell: 'Allgemeine Hochschulreife - Mathematik/Informatik' nach APO-BK Anlage D21) ist in der Jahrgangsstufe 12 zunächst eine Unterrichtsreihe zum Thema „Objektorientierte Anwendungsentwicklung" vorgesehen, wobei ausgehend von vorgestellten betrieblichen Situationen Klassenstrukturen und -beziehungen entwickelt und realisiert werden sollen. Es soll also der Weg von der realitätsnahen Problemstellung hin zu einem passenden Programm geübt werden -- d.h. es soll erlernt werden, wie man eine wirklichkeitsbezogene Aufgabenstellung erfasst, sie (evtl. schrittweise) in relevante und überschaubare Teilprobleme zergliedert und dazu passende Java-Klassen entwirft und im UML-Klassendiagramm festhält. Bei einzelnen Beispielen soll dieser Weg auch in einem vollständig zu programmierenden, sinnvoll strukturierten Programm münden.
Unter der Überschrift „Software Engineering" wird die Anwendungsentwicklung in der gleichen Jahrgangsstufe nochmal aufgegriffen und erweitert: hier sollen dann auch die grundlegenden Prinzipien des Software Engineering sowie Vorgehensmodelle der Systementwicklung besprochen werden. Dazu werden (betriebliche) Geschäftsprozesse durch [erweiterte] ereignisgesteuerte Prozess-Ketten ([e]EPK) beschrieben. Und es soll nicht nur die statische Struktur des schließlich geplanten Programms durch ein UML-Klassendiagramm verdeutlicht werden, sondern es sollen auch dynamische Prozesse durch UML-Sequenzdiagramme veranschaulicht werden. Insgesamt geht es also durchaus darum, auf die spätere berufliche Praxis eines Programmierers oder Systemanalytikers (bzw. einer Programmiererin oder Systemanalytikerin) vorzubereiten. So soll auch über die Arbeitsweise im Team gesprochen werden und der Themenkreis in der Jahrgangsstufe 13 durch eine Unterrichtsreihe „Projektmanagement und Projektpraxis" vervollständigt werden - an die sich dann eigene Softwareprojekte der Schülerinnen und Schüler anschließen.

Im Rahmen der Unterrichtseinheiten für die Klasse 12 werden also mindestens drei Arten grafischer Visualisierungshilfen benötigt und eingeführt: UML-Klassen- und Sequenzdiagramme sowie ereignisgesteuerte Prozess-Ketten. Der Einsatz wird im Folgenden an einer Beispielaufgabe gezeigt.


zum Seitenanfang / zum Seitenende

Situation und Aufgabe:
Eine Autovermietung will ihren Bestand sortieren



Der folgende Text beschreibt eine 'betriebliche Situation' und die Aufgabe:

Sie arbeiten in einem Software- und Systemhaus und betreuen u.a. die Autovermietung 'Speedy Cars' mit mehreren Filialen.

Bei 'Speedy Cars' sollen die Daten der einzelnen Kraftfahrzeuge innerhalb der Filialen verwaltet und nach Kennzeichen sortiert werden können. Außerdem soll die Zentrale der Autovermietung die Daten aller Fahrzeuge in einer zusätzlichen Datenstruktur ablegen, und zwar wieder nach Kennzeichen sortiert. Dabei bestehen Kfz-Kennzeichen aus einer 1 bis 3 Buchstaben langen Kennung für den Zulassungsbezirk, gefolgt von ein oder zwei Buchstaben und ein bis vier Ziffern. Bei der Sortierung nach Kennzeichen sollen die Kennzeichen eines Zulassungsbezirks zusammen bleiben und in einer sinnvollen Reihenfolge erscheinen.

Durch Einwegmieten kann sich im Laufe der Zeit die Zuordnung der Autos zu den Filialen ändern; zu bestimmten Zeitpunkten (bei denen keine Autos mehr unterwegs sind), sollen die aktuellen Bestände erneut filialweise sortiert und ebenfalls die Gesamtliste erneut sortiert angelegt werden können.




zum Seitenanfang / zum Seitenende

Modellierung und sinnvolle Klasseneinteilung



Die vorstehende, z.T. wenig präzise Formulierung kann so gedeutet werden, dass die Autovermietung mit einer Zentrale und mehreren Filialen modelliert werden soll. In den einzelnen Filialen gibt es offenbar Autos, von denen außer dem Kennzeichen sicher auch Hersteller und Modell sowie der km-Stand interessieren. Diese Autos müssen in einer geeigneten Datenstruktur gespeichert werden. Wird ein Auto vermietet, kommt es später in der gleichen oder einer anderen Filiale mit erhöhtem km-Stand zurück und muss wieder bzw. dort aufgenommen werden können. Außerdem muss es möglich sein, bei dem erhofften Wachstum von 'Speedy Cars' die Flotte zu vergrößern, d.h. weitere Fahrzeuge hinzu zu kaufen und zu speichern, um sie dann ebenfalls vermieten zu können.

Im normalen Vermietungsbetrieb ist sicher auch spannend, wie lange (und an wen!) ein Auto vermietet wird. Außerdem mögen verschiedene Fahrzeuge unterschiedliche Mietpreise haben. Und natürlich wird bei der Rückgabe auch kontrolliert, ob der Tank wieder voll ist oder auf Kundenkosten nachgefüllt werden muss bzw. ob der Kunde Beschädigungen verursacht hat. Diese letztgenannten Aspekte werden aber zunächst nicht in den Entwurf aufgenommen, da sie für die Sortieraufgabe unerheblich sind: kann man überhaupt die Daten jedes Autos nach dem Kennzeichen sortieren, so ist es letztlich egal, wie umfangreich der Datensatz ist. Auf die Modellierung des eigentlichen Vermietungsgeschäfts sowie die Verwaltung der Kundendaten oder auch der Angestellten wird ebenfalls (noch) völlig verzichtet.

D.h. aus der Problembeschreibung werden zunächst einige für die spezielle Aufgabenstellung 'Daten festhalten und sortieren' wichtig erscheinende Aspekte heraus gegriffen (und viele andere bewusst weggelassen). Außerdem empfiehlt es sich, die künftige Programmstruktur den tatsächlichen Objekten und Strukturen anzupassen und daher Klassen zu planen, die den realen Gegenständen oder Organisationseinheiten entsprechen. Weil im Text länger darauf eingegangen wurde, wie Kennzeichen aufgebaut sind, scheint dem Kennzeichen eine so große Bedeutung zuzukommen, dass auch hierfür eine eigene Klasse vorgesehen wird -- während andere im Alltag wichtige Teile des Autos, wie etwa der Motor, nicht durch eine Klasse repräsentiert werden, weil sie oben nicht erwähnt wurden und daher hier offenbar weniger interessant sind.
Ein erster Entwurf könnte deswegen z.B. folgendes Aussehen haben:

erstes Klassendiagramm für die Autovermietung
Dabei wurden die Typen der Attribute noch nicht genannt und noch keine Entscheidung über die Datenspeicher getroffen. Von den Methoden wurden bisher nur 'sprechende' Namen angegeben, wobei natürlich auch andere Entscheidungen möglich sind:

Neben nenneKennz(eichen), das offenbar ein Kennzeichen in der Form "K-B 423" oder "KA-X 4000" nennen soll, wurde hier eine Methode nenneAlsSortierSchlüssel vorgesehen, die eine Ausgabe wie "K~~-B~ ~423" bzw. "KA~-X~ 4000" erzeugen soll, die durch Auffüllen mit Leerzeichen '~' ein festes Format erhält. Nur wenn die mit der letzten Methode ausgegebenen Strings mit dem Java-Befehl compareTo lexikalisch sortiert werden, wird eine vernünftige Sortierreihenfolge entstehen. Mit der ersten Form würden Kennzeichen verschiedener Zulassungsbezirke vermischt.
Statt aber extra eine geeignete Form für das in Java eingebaute String-Sortieren zu erzeugen, hätte man alternativ auch für Kennzeichen eine eigene compareTo-Methode schreiben können, die berücksichtigt, dass "KA-X 4000" nicht zwischen "K-AX 1000" und "K-B 423" stehen soll -- und "K-B 423" vor "K-B 2007" (weil zahlenmäßig 423 < 2007 ist, während bekanntlich die Zeichenkettendarstellung der Ziffernfolgen ohne führendes Leerzeichen lexikalisch umgekehrt angeordnet würde: "2007" < "423").

Beim Auto soll sich zwischen Vermietung und Rückgabe der km-Stand ändern können; Filiale und Zentrale verfügen außerdem über die Sortiermöglichkeit.


zum Seitenanfang / zum Seitenende

Gewünschte Benutzer-Schnittstelle bzw. Oberfläche



Vor der tatsächlichen Programmierung muss man sich außerdem Gedanken über die Benutzerschnittstelle bzw. die gewünschte Oberfläche machen. Hier wurde zum Beispiel entschieden, eine AWT-Oberfläche mit Schaltflächen für die Durchmischung der Autos durch Vermietung, das Sortieren in den Filialen oder das Zusammenführen der sortierten Filialbestände zu einer zentralen sortierten Gesamtliste zu schaffen. Zusätzlich müssen Formular- bzw. Eingabefelder für die Daten neu aufzunehmender Autos da sein. In einem mehrzeiligen Anzeigefeld (TextArea) sollen schließlich die Datenbestände (etwa vor und nach der Sortierung) angezeigt werden können. Eine solche Möglichkeit zeigt das Bild

mögliche Oberfläche für das Autovermietungsprogramm

Für die Startdatei (nur mit der main-Methode) und die von ihr erzeugte und gestartete Oberfläche sind zwei zusätzliche Klassen nötig. In der Oberfläche soll nur ein Objekt firma nach dem Bauplan der Klasse Zentrale erzeugt werden, sodass in der Zentrale noch einige zusätzliche Methoden eingeplant werden müssen: Drückt man in der Oberfläche auf die Schaltfläche „Sortieren filialweise" kann - weil in der Oberfläche ja nur die firma vom Typ Zentrale bekannt ist - nur eine Methode von Zentrale aufgerufen werden, die ihrerseits in allen (an die Zentrale angeschlossenen) Filialen das (filialweise) Sortieren anstößt. In ähnlicher Weise muss eine zusätzliche Methode in Zentrale für das filialweise Anzeigen der Autos die Informationen aus den Filialen anfordern und zur Oberfläche durchreichen.




zum Seitenanfang / zum Seitenende

Technische Entscheidungen, Prototyp und UML-Klassendiagramm



Bisher wurde das Programm noch weitgehend als 'black box' beschrieben, sieht man mal von der Einteilung in Klassen ab. Es wurde aber noch nichts über die intern in den Klassen anzulegenden Datenstrukturen oder über sinnvolle Algorithmen in den projektierten Methoden gesagt. Nachdem also oben im Wesentlichen das Verhalten des Programms nach außen, also das Was und Wofür beschrieben wurde, soll jetzt das Wie und Womit betrachtet werden -- Jetzt wird festgelegt, welche programmtechnischen Möglichkeiten zur Erfüllung der geforderten Aufgaben eingesetzt werden sollen. Hier einige getroffene Entscheidungen:

Die vorstehenden Entscheidungen zusammen mit der Festlegung der Attributtypen sowie der Bestimmung der Rückgabetypen und Parameter der Methoden reichen längst, um in einem ersten Prototyping ein lauffähiges Programm zu erstellen -- auch wenn dort vielleicht zunächst die Sortiermethoden noch leer bleiben. Trotzdem kann man so schon das Zusammenwirken der Klassen testen, sich an den ersten Ausgaben erfreuen und die Neuaufnahme von Autos testen. Nur beim Betätigen der Sortier-Schaltflächen passiert eben noch nichts (es sollten aber eben auch kein Fehler entstehen). Oder man baut zunächst für das filialweise Sortieren den einfachen BubbleSort ein, selbst wenn ein höheres Sortierverfahren gefordert wird. Läuft diese vorläufige Fassung (eben der Prototyp), kann man später das einfache Sortierverfahren z.B. durch QuickSort ersetzen und sich außerdem eine geschickte Adaption von MergeSort für das Zusammenführen der sortierten Filialbestände in einer Reihung gesamt in der Zentrale überlegen.

Das Klassendiagramm hat jetzt folgende präzisierte (und endgültige) Gestalt:









vollständiges UML-Klassendiagramm der Autovermietung, erzeugt mit JUDE

(Das Diagramm wurde übrigens mit der gratis erhältlichen Community Edition des Programms JUDE [=Java-oriented UML- Diagram Editor] der japanischen Firma Change Vision erstellt. Der Download der etwa 6 MB großen Setup-Datei ist nach kostenloser Registrierung möglich).

Wie in normgerechter UML-Darstellung üblich, sind die oben beschriebenen Reihungen nicht direkt sichtbar: Dass in einer Filiale viele Autos gespeichert sind, wird im Bild durch die Aggregation zwischen der Klasse Filiale und der Klasse Auto verdeutlicht (wobei der * am Ende des Aggregationspfeils darauf hinweist, dass eine Filiale viele Autos hat oder haben kann). Nur der angegebene Name ~auto zeigt, dass ein Attribut namens auto (wegen ~ ohne besondere Angaben zur Sichtbarkeit) die Aggregation im Programmtext realisiert. Wie wir unten sehen, ist auto eine Reihung (array). Auch die Attribute, die die anderen Verbindungen letztlich herstellen, sind nicht innerhalb der Klassen, sondern bisher nur an den Pfeilen benannt und hier noch ohne genaue Typspezifikation, die sich aus den Multipliziäten und den beteiligten Klassen ergibt (wobei natürlich an Stelle einer Reihung auch eine dynamische Liste oder eine andere Datenstruktur zur Aufnahme mehrerer Autos möglich wäre).


zum Seitenanfang / zum Seitenende

Aufruf-Weiterleitung und UML-Sequenzdiagramm



Oben -- im Abschnitt über die Oberfläche -- wurde bereits erwähnt, dass beispielweise ein Knopfdruck in der Oberfläche einen Auftrag an die Zentrale auslöst, der von dort an die Filialen weiter geleitet wird. Um aber dort die Autos sortieren zu können, muss man deren Kennzeichen bzw. sogar die Nummer formatiert als Sortier-Schlüssel kennen. D.h. hier sind praktisch alle Klassen mit ihren Methoden beteiligt. Diese bei bestimmten Aktionen auszulösenden Aktionen können durch ein Sequenzdiagramm veranschaulicht werden, wobei allerdings nicht deutlich wird, dass mehrere Filialen den gleichen sortiere()-Auftrag kriegen, sich quicksort vielfach rekursiv aufruft oder während des Sortierens viele Autokennzeichen (von den vielen verschiedenen Autos) überprüft werden. Ich habe versucht, dies durch einen Stern * (ähnlich wie an den Assoziationspfeilen des Klassendiagramms) anzudeuten. Vermutlich ist aber ein Beschreibungstext besser geeignet.

Auch das Sequenzdiagramm wurde mit der bereits im letzten Abschnitt erwähnten Software JUDE erzeugt (Link zur Homepage auch ganz unten bei den Verweisen!). Um das Strichmännchen-Piktogramm ('actor') für den Menschen zu wählen, muss man zuerst ein UseCase-Diagramm neu öffnen und dort ein Strichmännchen von der Werkzeugleiste auf die Arbeitsfläche ziehen. Dann wird es automatisch auch links in den Objekt-Baum ('Structure') eingetragen und kann von dort ins Sequenzdiagramm gezogen werden (wo es in der Werkzeugleiste leider fehlt und daher nicht direkt gewählt werden kann):

UML-Sequenzdiagramm für die Autovermietung, erzeugt mit JUDE

Ob ein Sequenzdiagramm für die Programmentwicklung wirklich viel bringt, sei dahin gestellt. Zumindest verdeutlicht es nochmal die mögliche Vielzahl hintereinander ausgelöster Aktionen und mag daran erinnern, wo auf geringen Zeitaufwand geachtet werden muss. Allerdings wird man normalerweise nicht alle möglichen Aktionen in einem komplexeren Programm durch Sequenzdiagramme darstellen wollen und können, zumal immer nur ein Ablauf für konkrete Objekte gezeigt werden kann, während Wiederholungen oder Alternativen durch die Standarddarstellung kaum berücksichtigt werden können.


zum Seitenanfang / zum Seitenende

Beispiel des vollständigen Java-Programmtextes



Nach der langen Diskussion der Entwurfsentscheidungen soll der schließlich entwickelte und z.T. noch wenig kommentierte, aber fertige Programmtext nicht fehlen: Das Problem ist vollständig implementiert; neben dem jetzt verwendeten Quicksort ist vom Prototyping noch das eigentlich nicht mehr benötigte BubbleSort übrig geblieben, und in der Methode zum Bewegen der Autos finden sich noch Ausgabeanweiungen als Hilfe für ein früher nötiges Debugging. Aber jetzt läuft alles wie vorgesehen.

Jede Klasse steht in einer eigenen, gleichnamigen .java-Datei.



// AV = Projekt AutoVermietung -- Krell (www.r-krell.de), 2007, für IF12M

public class AV_Start
{
  
public static void main (String[] s)
  {
    AV_Oberflaeche gui = 
new AV_Oberflaeche();
    gui.führeAus();
  }
}


zum Seitenanfang   <<   <   >   >>   zum Seitenende


// AV = Projekt AutoVermietung -- Krell (www.r-krell.de), 2007, für IF12M

import java.awt.*;
import java.awt.event.*;

public class AV_Oberflaeche extends Frame  // Frame ist die Fensterklasse
{
  Button btBewegen, btFiliallisten, btFilialSortieren, btGesamtSortieren, btGesamtliste,
         btDazu, btReset;
  TextField tfFil, tfHerst, tfMarke, tfFarbe, tfKm, tfOrt, tfBuchst, tfNr;
  TextArea taAusgabe;

  AV_Zentrale firma = 
new AV_Zentrale(); // (Verbindung zum) Objekt der Zentrale

  
public void richteFensterEin() // Fenster initalisieren und beschreiben
  
{
    
//WindowsListener hinzufügen, damit Schließsymbol 'x' funktioniert
    
addWindowListener (
      
new WindowAdapter ()
      {
        
public void windowClosing (WindowEvent ereignis)
        {                
//ersetzt bisher leere Methode
          
setVisible (
false);
          dispose();
          System.exit(
0);
        }
      }
    ); 
// runde Klammer vom Windowlistener geschlossen;

    
setTitle(
"Autovermietung"); // Fenster mit Titel versehen
    
setSize (
820,600); //Fenstergröße (Breite und Höhe in Pixeln)festlegen
  
}


  
private void richteKnöpfeEin()
  {
    
// Knöpfe definieren und beschriften (Größe richtet sich nach Beschriftung)
    
btBewegen = 
new Button ("Autos vermieten und zurück nehmen");
    btFiliallisten = 
new Button ("Autos filialweise zeigen");
    btFilialSortieren = 
new Button ("Sortieren filialweise");
    btGesamtSortieren = 
new Button ("Sortieren gesamt");
    btGesamtliste = 
new Button ("Gesamtbestand der Autos zeigen");
    btDazu = 
new Button ("O.K.");
    btReset = 
new Button ("Reset");

    
//Funktion der Knöpfe festlegen
    
btBewegen.addActionListener (
      
new ActionListener ()
      {
        
public void actionPerformed (ActionEvent e)
        {
          firma.lasseFahren();
          taAusgabe.append (
"Autos wurden bewegt.\n");
        }
      });
 // runde Klammer (von addActionListener)
      
    
btFiliallisten.addActionListener (
      
new ActionListener ()
      {
        
public void actionPerformed (ActionEvent e)
        {
          taAusgabe.append (firma.zeigeAutosFilialweise());
        }
      }); 
// runde Klammer (von addActionListener)
      
    
btFilialSortieren.addActionListener (
      
new ActionListener ()
      {
        
public void actionPerformed (ActionEvent e)
        {
          firma.sortiereAutosFilialweise();
// vgl. Sequenzdiagramm!
          taAusgabe.append (
"Autos wurden in den Filialen sortiert.\n");
        }
      }); 
// runde Klammer (von addActionListener)
      
    
btGesamtSortieren.addActionListener (
      
new ActionListener ()
      {
        
public void actionPerformed (ActionEvent e)
        {
          firma.sortiereAutosInsgesamt();
          taAusgabe.append (
"Eine (sortierte) Gesamtliste aller Autos wurde erstellt.\n");
        }
      }); 
// runde Klammer (von addActionListener)
      
    
btGesamtliste.addActionListener (
      
new ActionListener ()
      {
        
public void actionPerformed (ActionEvent e)
        {
          taAusgabe.append (firma.zeigeAutosInsgesamt());
        }
      }); 
// runde Klammer (von addActionListener)
      
    
btReset.addActionListener (
      
new ActionListener ()
      {
        
public void actionPerformed (ActionEvent e)
        {
          tfFil.setText(
"");
          tfHerst.setText(
"");
          tfMarke.setText(
"");
          tfFarbe.setText(
"");
          tfKm.setText(
"");
          tfOrt.setText(
"");
          tfBuchst.setText(
"");
          tfNr.setText(
"");
        }
      }); 
// runde Klammer (von addActionListener)

    
btDazu.addActionListener (
      
new ActionListener ()
      {
        
public void actionPerformed (ActionEvent e)
        {
          
if (tfFil.getText().equals("") || tfHerst.getText().equals("") || tfMarke.getText().equals("") ||
              tfFarbe.getText().equals(
"") || tfKm.getText().equals("") || tfOrt.getText().equals("") ||
              tfBuchst.getText().equals(
"") || tfNr.getText().equals(""))
          {
            taAusgabe.append (
"** Erst alle Felder ausfüllen, bevor Auto aufgenommen werden kann!\n");
          }
          
else
          
{
            
int ziel = Integer.parseInt(tfFil.getText());
            
if (ziel>=&& ziel<firma.filZahl)
            {
              
int nr = Integer.parseInt (tfNr.getText());
              AV_Kennzeichen test = 
new AV_Kennzeichen (tfOrt.getText(), tfBuchst.getText(), nr);
              
if (test.gültig())
              {
                
int km = Integer.parseInt(tfKm.getText());
                
if (firma.neuesAuto(ziel,tfHerst.getText(), tfMarke.getText(), tfFarbe.getText(), km,
                    tfOrt.getText(), tfBuchst.getText(), nr))
                {
                  taAusgabe.append (
"Auto erfolgreich aufgenommen.\n");
                }
                
else
                
{
                  taAusgabe.append (
"** Probleme bei der Aufnahme: Zuviel, doppeltes Kennzeichen,... ?\n");
                }
              }
              
else
              
{
                taAusgabe.append (
"** Ungültiges Kennzeichen.\n");
              }
            }
            
else
            
{
              taAusgabe.append (
"** Falsche Filialnummer -- richtig 0.."+(firma.filZahl-1)+"\n");
            }

          }
        }
      });
 // runde Klammer (von addActionListener)
      
   
}

  
public void richteEingabezeilenEin()
  {
    tfFil = 
new TextField("0",1);
    tfHerst = 
new TextField("",13);
    tfMarke = 
new TextField("",16);
    tfFarbe = 
new TextField("",12);
    tfKm = 
new TextField("5000",4);
    tfOrt = 
new TextField("",1);
    tfBuchst = 
new TextField("",2);
    tfNr = 
new TextField("",3);
   }

  
public void richteTextfensterEin ()
  {
    taAusgabe = 
new TextArea("Autovermietung:\n"+firma.zeigeAutosFilialweise(),30,110);

    taAusgabe.setEditable(
false);
    // bei true kann der Textfensterinhalt auf dem Bildschirm vom Benutzer verändert
    // werden (Normalfall), bei false nicht.

   
}

  
public void führeAus ()
  {
    richteFensterEin();           
// Aufruf der Methoden zum Erzeugen der
    
richteKnöpfeEin();          
  // Bildschirmobjekte 
    
richteEingabezeilenEin();
    richteTextfensterEin();

    setLayout (
new FlowLayout()); // innere Unterteilung des Bildschirmfensters

    
add (btBewegen);              
// Anordnung der Objekte auf dem Bildschirm
    
add (btFiliallisten);
    add (btFilialSortieren);
    add (btGesamtSortieren);
    add (btGesamtliste);

    add (
new Label("Neues Auto für Filiale")); add(tfFil);
    add (
new Label("  Hersteller:")); add(tfHerst);
    add (
new Label("  Typ:")); add(tfMarke);
    add (
new Label("  Farbe:")); add(tfFarbe);
    add (
new Label("  Kilometer-Stand:")); add(tfKm);
    add (
new Label("km   Kennzeichen:")); add(tfOrt);
    add (
new Label("-")); add(tfBuchst);
    add (tfNr);
    add (btDazu);
    add (btReset);

    add (taAusgabe);

    setVisible(
true);          // Macht Bildschirm mit allen angeordneten
      // Objekten sichtbar und startet damit das Programm

  
}
}


zum Seitenanfang   <<   <    >   >>   zum Seitenende

// AV = Projekt AutoVermietung -- Krell (www.r-krell.de), 2007, für IF12M

public class AV_Zentrale
{
  AV_Filiale[] filiale;
  
int filZahl=0;
  AV_Auto[] gesamt;
  
int gesZahl = 0;
  
  
public AV_Zentrale()
  {
    filZahl = 
2;
    filiale = 
new AV_Filiale[filZahl];
    gesamt  = 
new AV_Auto[filZahl*20];
    filiale[
0] = new AV_Filiale ("Düsseldorf","D");
    filiale[
1] = new AV_Filiale ("Neuss","NE");
    filiale[
0].neuesAuto ("VW","Golf","silber",8500,"D","AA",467);
    filiale[
0].neuesAuto ("Ford","Focus","silber",7300,"D","XB",1237);
    filiale[
0].neuesAuto ("Opel","Meriva","blau",11000,"D","CC",4967);
    filiale[
0].neuesAuto ("Mazda","6","grün",3400,"ME","FG",8967);
    filiale[
1].neuesAuto ("VW","Passat","silber",500,"NE","BX",2341);
    filiale[
1].neuesAuto ("Toyota","Corolla","silber",13300,"NE","BM",2637);
    filiale[
1].neuesAuto ("Ford","Ka","grün",17000,"NE","M",7927);
    filiale[
1].neuesAuto ("BMW","318","rot",9400,"NE","FG",61);
  }

  
public boolean neuesAuto (int fil, String hersteller, String modell, String farbe, int kmStand, String ort, String bst, int nr)
  {
    
if (fil >= && fil < filZahl)
    {
      
return (filiale[fil].neuesAuto(hersteller, modell, farbe, kmStand, ort, bst, nr));
    }
    
return (false);
  }
  
  
public void lasseFahren ()
  {
    
for (int i=0; i < 20*Math.random(); i++)
    {
      
int startFil = (int) (filZahl*Math.random());
      
int zielFil  = (int) (filZahl*Math.random());
      
int gefahren = (int) (1500*Math.random());
      AV_Auto pkw = filiale[startFil].entliehen();
      System.out.println(
"Start="+startFil+" Ziel="+zielFil+" +"+gefahren+" km -- "
                         
+pkw.zeigeAuto());
      filiale[zielFil].zurück(pkw, gefahren);
    }
  }
  
  
public String zeigeAutosFilialweise()
  {
    String aus = 
"";
    
for (int f=0; f<filZahl; f++)
    {
      aus = aus + filiale[f].zeigeAlle();
    }
    
return (aus+"\n");
  }
  
  
public void sortiereAutosFilialweise()
  {
    
for (int f=0; f<filZahl; f++)
    {
      filiale[f].sortiere();
    }
  }
  
  
public void sortiereAutosInsgesamt() // MergeSort
  
{
    
if (filZahl == 1)  // bei einer Filiale ist der Gesamtbestand
      // gleich dem Bestand der einen Filiale (die den Index 0 hat!)

    
{
      gesZahl = filiale[
0].autoZahl;
      
for (int i=0; i < gesZahl; i++)
      {
        gesamt[i] = filiale[
0].auto[i];
      }
    }
    
    
else if (filZahl > 1)  // bei mehreren Filialen wird eine Filiale nach
      // der anderen per 2er-MergeSort zum bisherigen Bestand dazu getan

    
{
      AV_Auto[] bisher = 
new AV_Auto [filZahl*20];
      
int bisZahl = filiale[0].autoZahl;
      
for (int i=0; i < bisZahl; i++)  // bisheriger Bestand = Bestand von Filiale 0
      
{
        bisher[i] = filiale[
0].auto[i];
      }
      
      
for (int fi=1; fi < filZahl; fi++) // MergeSort: Filiale mit Index fi zum bisherigen Bestand dazu
      
{
        gesZahl = 
0;
        
int bisPos = 0;
        
int filPos = 0;
        
while (bisPos < bisZahl && filPos < filiale[fi].autoZahl)
        {
          
if (bisher[bisPos].nenneNr().compareTo(filiale[fi].auto[filPos].nenneNr())<0)
          {
            gesamt[gesZahl] = bisher[bisPos];
            gesZahl++;
            bisPos++;
          }
          
else
          
{
            gesamt[gesZahl] = filiale[fi].auto[filPos];
            gesZahl++;
            filPos++;
          }
        }
        
while (bisPos < bisZahl)
        {
          gesamt[gesZahl] = bisher[bisPos];
          gesZahl++;
          bisPos++;
        }
        
while (filPos < filiale[fi].autoZahl)
        {
          gesamt[gesZahl] = filiale[fi].auto[filPos];
          gesZahl++;
          filPos++;
        }

        bisZahl = gesZahl;   
// neues Gesamt ist der bisherige Bestand für nächsten for-Durchlauf
        
for (int i=0; i < bisZahl; i++)
        {
          bisher[i] = gesamt[i];
        }
      }
    }
  }
  
  
public String zeigeAutosInsgesamt()
  {
    String ausgabe = 
"\nGesamtbestand aller Autos:\n";
    
for (int i=0; i<gesZahl; i++)
    {
      
if (i<10)
       {
         ausgabe = ausgabe+
' ';
       }
       ausgabe = ausgabe + 
" "+i+".) "+gesamt[i].zeigeAuto()+"\n";
     }
    
return (ausgabe);
  }
  
}


zum Seitenanfang   <<   <    >   >>   zum Seitenende



 // AV = Projekt AutoVermietung -- Krell (www.r-krell.de), 2007, für IF12M

 
public class AV_Filiale
 {
   
static int lfdNr = 0;

   String ortsname, ortskürzel;
   
int filialNr;
   AV_Auto[] auto = 
new AV_Auto[20];
   
int autoZahl = 0;
   
   
public AV_Filiale (String stadtname, String stadtkürzel)
   {
     ortsname = stadtname;
     ortskürzel = stadtkürzel;
     filialNr = lfdNr;
     autoZahl = 
0;
     lfdNr++;
   }
   
   
public boolean neuesAuto (String hersteller, String modell, String farbe, int kmStand, String ort, String bst, int nr)
   {
     
if (autoZahl <= 20)
     {
       auto[autoZahl] = 
new AV_Auto(hersteller, modell, farbe, kmStand, ort, bst, nr, filialNr);
       
for (int i=0; i<autoZahl; i++)
       {
         String neueNr = auto[autoZahl].nenneNr();
         
if (auto[i].nenneNr().equals(neueNr))
         {
           
return (false);
         }
       }
       autoZahl++;
       
return (true);
     }
     
return (false);
   }
   
   
public AV_Auto entliehen (int lfdNr)
   {
     AV_Auto entliehen = auto[lfdNr];
     autoZahl--;
     
for (int i=lfdNr; i<autoZahl; i++)
     {
       auto[i] = auto[i+
1];
     }

     
for (int i=0; i<autoZahl; i++)
     {
       System.out.println(
" - "+i+".) "+auto[i].zeigeAuto());
     }

     
return (entliehen);
   }
   
   
public AV_Auto entliehen ()
   {
     
return (entliehen((int)(Math.random()*autoZahl)));
   }
   
   
public boolean zurück (AV_Auto wagen, int km)
   {
     
if (autoZahl <= 20 && wagen != null)
     {
       auto[autoZahl] = wagen;
       auto[autoZahl].erhöheUmKm (km);
       autoZahl++;

     
for (int i=0; i<autoZahl; i++)
     {
       System.out.println(
" + "+i+".) "+auto[i].zeigeAuto());
     }

       
return (true);
     }
     
return (false);
   }
   
   
public String zeigeAlle()
   {
     String ausgabe = 
"Fahrzeug-Bestand der Filiale "+filialNr+" in "+ortsname+":\n";
     
for (int i=0; i<autoZahl; i++)
     {
       
if (i<10)
       {
         ausgabe = ausgabe+
' ';
       }
       ausgabe = ausgabe + 
" "+i+".) "+auto[i].zeigeAuto()+"\n";
     }
     
return (ausgabe);
   }
   
   
private void tausche (int i, int j)
   {
     AV_Auto hilf = auto[i];
     auto[i] = auto[j];
     auto[j] = hilf;
   }
   
   
public void sortiere_bubbleSort()
   {
     
for (int durchg=1; durchg<autoZahl; durchg++)
     {
       
for (int i=0; i<autoZahl-durchg; i++)
       {
         
if (auto[i].nenneNr().compareTo(auto[i+1].nenneNr())>0)
         {
           tausche (i,i+
1);
         }
       }
     }
   }
   
   
public void sortiere() // Rahmen für QuickSort
   
{
     quicksort (
0, autoZahl-1);
   }
   
   
private void quicksort (int links, int rechts)
   {
     
if (links < rechts)
     {
       
int grün_vlnr = links;  // grüner Zeiger/Index wandert von links nach rechts
       
int rot_vrnl  = rechts; // roter Zeiger/Index wandert von rechts nach links
       
int trenn_pivot = (links+rechts)/2// Index des Trenn- bzw. Pivotelements;
        // kann willkürlich irgendwo zwischen links und rechts (einschl.) gewählt werden


       
while (grün_vlnr <= rot_vrnl)
       {
          
while ((grün_vlnr<rechts) && (auto[grün_vlnr].nenneNr().compareTo(auto[trenn_pivot].nenneNr())<0))
          {
            grün_vlnr++;
          }
          
while ((rot_vrnl>links) && (auto[rot_vrnl].nenneNr().compareTo(auto[trenn_pivot].nenneNr())>0))
          {
            rot_vrnl--;
          }
          
if (grün_vlnr <= rot_vrnl)
          {
            tausche (grün_vlnr, rot_vrnl);
            grün_vlnr++;
            rot_vrnl--;
          }
       }
       
       quicksort (links, rot_vrnl);
       quicksort (grün_vlnr, rechts
);
     }
   }
 }


zum Seitenanfang   <<   <    >   >>   zum Seitenende



// AV = Projekt AutoVermietung -- Krell (www.r-krell.de), 2007, für IF12M

public class AV_Auto
{
  
private String hersteller, modell, farbe;
  
private int kmStand;
  
private AV_Kennzeichen nummer;
  
private int filiale;
  
  
public AV_Auto (String hersteller1, String modell1, String farbe1, int kmStand1, String ort, String bst, int nr, int fil)
  {
    hersteller = hersteller1;
    modell = modell1;
    farbe = farbe1;
    kmStand = kmStand1;
    nummer = 
new AV_Kennzeichen (ort, bst, nr);
    filiale = fil;
  }
  
  
public void erhöheUmKm (int gefahreneKm)
  {
    kmStand = kmStand + gefahreneKm;
  }
  
  
public String zeigeAuto()
  {
    String ausgabe = hersteller + 
" " + modell + ", "+farbe+", \""+nummer.nenneKennzeichen()+"\", "
                     
+kmStand +" km. ("+filiale+")";
    
return (ausgabe);
  }
  
  
public String nenneNr ()
  {
    
return (nummer.sortierSchlüssel());
  }
}


zum Seitenanfang   <<   <    >   >>   zum Seitenende


// AV = Projekt AutoVermietung -- Krell (www.r-krell.de), 2007, für IF12M

public class AV_Kennzeichen
{
  
private String ort, buchst;
  
private int zahl;
  
  
private boolean nurBuchstaben (String test)
  {
    test = test.toUpperCase();
    
boolean okay = true;
    
for (int i=; i<test.length() && okay ; i++)
    {
      okay = okay && test.charAt(i) >= 
'A' && test.charAt(i) <= 'Z';
    }
    
return (okay);
  }
  
  
public void setzeOrt (String stadt)
  {
    
if (stadt != null && stadt.length() >=&& stadt.length() <= && nurBuchstaben(stadt))
    {
      ort = stadt.toUpperCase();
    }
    
else
    
{
      ort = 
"???";
    }
  }
  
  
public void setzeBuchstaben (String bst)
  {
    
if (bst != null && bst.length() >=&& bst.length() <= && nurBuchstaben(bst))
    {
      buchst = bst.toUpperCase();
    }
    
else
    
{
      buchst = 
"??";
    }
  }
  
  
public void setzeZahl (int nr)
  {
    
if (nr>&& nr<=9999)
    {
      zahl = nr;
    }
    
else
    
{
      zahl = -
1;
    }
  }
  
  
public AV_Kennzeichen (String stadt, String bst, int nr)
  {
    setzeOrt (stadt);
    setzeBuchstaben (bst);
    setzeZahl (nr);
  }
  
  
public String nenneKennzeichen ()
  {
    
return (ort+"-"+buchst+" "+zahl);
  }
  
  
public String sortierSchlüssel () // formatierte Ausgabe für String-Sortieren
  {
    String schlüssel = ort;
    
for (int i=ort.length(); i<3; i++)
    {
      schlüssel = schlüssel+
' ';
    }
    schlüssel = schlüssel+buchst;
    
if (buchst.length() < 2)
    {
      schlüssel = schlüssel+
' ';
    }
    
if (zahl < 1000)
    {
      schlüssel = schlüssel+
' ';
    }
    
if (zahl < 100)
    {
      schlüssel = schlüssel+
' ';
    }
    
if (zahl < 10)
    {
      schlüssel = schlüssel+
' ';
    }
    schlüssel=schlüssel+zahl;
    
return (schlüssel);
  }
  
  
public boolean gültig()
  {
    
return (ort.indexOf('?')<&& buchst.indexOf('?')<&& zahl>0);
  }
}

Abschließend sei angemerkt, dass der JavaEditor (die hier verwendete und auf meiner Seite „Informatik mit Java, Teil a)" beschriebene Entwicklungsumgebung) aus dem geschriebenen Programmtext automatisch ein UML-Klassendiagramm erzeugen kann. Anders als im von JUDE erzeugten UML-Diagramm (s.o., Abschnitt „Technische Entscheidungen, Prototyp und UML-Klassendiagramm") bleiben hier aber in den Klassen alle Attribute stehen, auch wenn diese die Verbindung zu weiteren Klassen herstellen (und daher eigentlich schon durch die Pfeile ausgedrückt werden, die allerdings im JavaEditor von Hand gezogen werden müssen). Damit ist dieses Diagramm nicht ganz normgerecht und -- da die Pfeile an den Klassen und nicht an den entsprechenden Attributen beginnen -- wären bei einer Umsetzung in Programmtext Missverständnisse bzw. unnötige Doppelungen bei Attributen möglich.

UML-Klassendiagramm der Autovermietung, erzeugt mit dem JavaEditor


zum Seitenanfang / zum Seitenende

Geschäftsprozessmodellierung I (mit eEPKs)



Trotz der vielen Programmzeilen bildet das oben entwickelte Programm nur einen kleinen Ausschnitt aus dem Alltag der Autovermietung ab. Einige Grenzen des Modells wurden ja schon im Abschnitt „Modellierung und sinnvolle Klasseneinteilung" angesprochen. Tatsächlich könnte man den programmierten Geschäftsprozess durch folgende ereignisgesteuerte Prozesskette (EPK) zusammenfassend beschreiben:

Zugegeben, man könnte diesen Teil doch etwas differenzierter betrachten und durch eine erweiterte ereignisgesteuerte Prozesskette (mit Informations- und Organisationsobjekten) beschreiben:

eEPK für die Autovermietung, erzeugt mit dem Diagram Designer

Dabei wurde im Diagramm bewusst darauf verzichtet, die Simulation des Fahrbetriebs durch die Taste „Autos vermieten und zurück nehmen" mit einzuzeichnen. Normalerweise geschieht die Bewegung der Fahrzeuge ja nicht durch das oder durch ein Programm, sondern durch die Kunden.

Zum Zeichnen der eEPKs eignet sich übrigens recht gut das abgebildete Freeware-Programm Diagram Designer von MeeSoft mit der Palette 'Polygons'. Der Download von der Autoren-Webseite ist nur etwa 1,3 MB groß; mit dem zusätzlichen Languagepack (160 kB) kann von der englischen auch auf die deutsche Oberfläche umgestellt werden.
Natürlich sind auch andere Zeichenprogramme wie etwa das kleine TeeTree-Office (s. Verweise), die Komponente Draw aus dem kostenlosen OpenOffice-Paket oder viele kommerzielle Programme hilfreich.




zum Seitenanfang / zum Seitenende

Geschäftsprozessmodellierung II (mit eEPKs)



Spannender und sinnvoller als das nachträgliche grafische Veranschaulichen, welchen Teil des Vermietungsgeschäfts das Programm simuliert, ist natürlich die frühzeitige Erfassung der (bisherigen) Abläufe im Geschäftsbetrieb (des Ist-Zustands) und die Beschreibung durch eine eEPK vor dem Programmieren. Wenn nach dieser Veranschaulichung Schwachstellen und Umwege im Geschäftsbetrieb aufgedeckt werden, sollte erst der Soll-Zustand des idealen oder zumindest verbesserten Geschäftsprozesses (z.B. wieder mit eEPK oder auch mit einem UML-Aktivitätsdiagramm, das aber in den Richtlinien für den Unterrischt z.Z. nicht vorgesehen ist) beschrieben werden. Dann können die Teile des vereinfachten Ablaufs, die künftig durch ein Programm übernommen werden können oder sollen, markiert werden und so das Software-Engineering bzw. die objektorientierte Anwendungsentwicklung auf solider Grundlage begonnen werden.

Als Ergänzung soll noch die Beschreibung des typischen Ausleihvorgangs eines Autos durch einen Kunden oder eine Kundin angefangen werden -- hier in der so genannten 4-Sichten-Variante der eEPK, wo in einer Spalte ('Sicht') die Informationsobjekte, in der nächsten Spalte die Ereignisse, in einer davon getrennten Spalte die Funktionen und schließlich in der 4. Spalte die handelnden Organisationseinheiten aufgeführt sind. Verbindungen mit Organisationseinheiten werden wie oft nur dann extra gezeichnet, wenn sich etwas ändert: die Verbindung vom Angestellten zur Funktion „Führerschein überprüfen" wäre richtig, gilt aber nach der ersten Verbindung zu „Kunden nach Wünschen fragen" weiterhin implizit und muss nicht unbedingt gezeichnet werden. Der Geschäftsprozess ist natürlich noch nicht fertig modelliert: er würde erst mit der Aushändigung des Autos enden und muss formal immer mit einem Ereignis abgeschlossen werden. Der Rest bleibt der Leserin/dem Leser als Übung!

eEPK (4-Sichten-Darstellung) für den Beginn von Mietverhandlungen, erstellt mit Diagram Designer

Eine direkte Umsetzung dieses Geschäftsprozesses in ein Programm wird natürlich nicht geplant: die Kunden sollen nicht durch den Computer ersetzt werden.
(Die vorstehende eEPK wurde ebenfalls mit dem Meesoft Diagram Designer erstellt. Ein Link auf eine weitere eEPK findet sich unten auf dieser Seite bei den Verweisen, nämlich als Beispiel für den Gebrauch von OpenOffice Draw als Zeichenwerkzeug. Das Diagramm dort ist allerdings nicht nach Sichten spaltenweise sortiert - und trotzdem gut lesbar).


zum Seitenanfang / zum Seitenende

Klausur

Mitte Mai 2007 wurde für die Berufskollegs in Nordrhein-Westfalen eine Probeklausur zentral gestellt werden -- genau zum hier behandelten Themenkomplex der Softwareentwicklung mit der grafischen Veranschaulichung durch Klassen- und Sequenzdiagramme sowie durch ereignisgesteuerte Prozessketten. Leider wird die Klausur, obwohl längst geschrieben, offenbar nicht ins Netz gestellt. Auch im Juni 2008 waren nur die Ankündigungen (inzwischen unter neuere Adresse) auf http://www.standardsicherung.schulministerium.nrw.de/abitur-bk/fach.php?fach=11 verfügbar, dort speziell die Datei Hinweise für die Probeklausur (pdf, 44 kB). Dem Vernehmen nach muss wegen der Angst, eventuell die (Urheber-)Rechte von Autoren der Aufgaben oder der beigefügten Diagramme zu verletzen, auf eine allgemeine Veröffentlichung verzichtet werden. Die Aufgaben dürfen daher nur im Unterricht Schülerinnen und Schülern zugänglich gemacht werden.


zum Seitenanfang / zum Seitenende

Verweise

Bevor Sie einen der folgenden Verweise anklicken, sollten Sie erst meine Seite zu Ihren Favoriten hinzufügen oder ein Lesezeichen setzen, damit Sie anschließend sicher hierher zurück finden! Natürlich kann ich für den Inhalt fremder Seiten keine Verantwortung übernehmen. Wenn ein Verweis nicht funktioniert, dort inzwischen andere Inhalte zu sehen sind oder wenn Sie mir weitere gute Seiten empfehlen können, bitte ich um eine kurze Nachricht!

Hinweis: die fremden Seiten werden in einem neuen Browser-Fenster geöffnet. Geschieht beim Anklicken eines Verweises scheinbar nichts, wird das neue Fenster vermutlich vom aktuellen Fenster verdeckt -- bitte per Task-Leiste ins neue Fenster wechseln!

Informatik mit Java, Teil a) Auf meiner ersten Java-Seite befinden sich im Text Hinweise zu benötigter und sinnvoller Software einschließlich Download-Adressen, u.a. vom oben erwähnten JavaEditor
Informatik mit Java, Übersicht Inhaltsverzeichnis (auf meiner Informatik-Hauptseite) mit Übersicht über alle meine Java-Seiten
Wikipedia: UML UML (= Unified Modeling Language) bei Wikipedia
Schäling: UML Online-Buch von B. Schäling: „Der moderne Software-Entwicklungsprozess mit UML", Kapitel 4 = Klassendiagramme
UML-Tutorial UML-Tutorial von Prof. Dumke, Uni Magdeburg
UML-Einführung Einführung in die objektorientierte Modellierung mit UML von Prof. Forbig, Uni Rostock
jeckle.de Übersicht über mehr als 100 UML-Diagramm-Tools von Mario Jeckle; außerdem Standards, UML-Spezifikationen, deutsche Sprachregelungen und mehr
oose.de UML-Notationsübersicht zum Download und weitere Infos im Service-Portal UML
Wikipedia: EPK Ereignisgesteuerte Prozessketten bei Wikipedia
EPK-Lernvideos und Tests Video- und Flash-gestützte Einführung in (e)EPKs von der Fachhochschule Basel, jetzt mit neuer URL (Auf der Webseite unter „Wissensvermittlung" klicken!)
Vorlesungsfolien EPK 25 Folien zu einer Wirtschaftsinformatik-Vorlesung über EPKs an der Uni Bremen (müssen immer vom Inhaltsverzeichnis aus einzeln angeklickt werden)
Geschäftsprozesse Lehrerfortbildung „Geschäftsprozesse in der Schule" der Landesakademie Baden-Württemberg
Change Vision Homepage des UML-Editors JUDE (gratis: Community Edition nach kostenloser Registrierung; ca 6 MB Download)
M. Vinther, MeeSoft Homepage des Grafik-Tools „Diagram Designer" (Freeware, 1.3 MB), deutsche u.a. Sprachdateien im zusätzlichen Language Pack (171 kB), weitere Zeichenobjekt-Paletten unter 'Template Palettes' (ca. 180 kB).
steema.com Homepage des Grafik-Tools „TeeTree Office" (Freeware, 1 MB) (ich hatte Probleme mit Pfeilen)
Dia-Zeichenprogramm Freeware-Zeichenprogramm mit vielen Paletten („Objektbögen"), zusammen mit dem benötigten GTK+ allerdings 12 MB Downloadumfang und hier auch nicht besser als der MeeSoft „Diagram Designer"
(de.)openoffice.org (dt.) Homepage von OpenOffice. Gesamtdownload fast 100 MB; das Teilprogramm Draw ist für Diagramme gut geeignet (ersetzt etwa das kommerzielle MS Visio). [Einen Eindruck von Draw vermittelt diese Schüler-EPK von der Autorückgabe (ggf. vergrößern!)]


zurück zur Informatik-Hauptseite


zum Anfang dieser Seite
Willkommen/Übersicht  -  Was ist neu?  -  Software  -  Mathematik  -  Physik  -  Informatik  -   Schule: Lessing-Gymnasium und -Berufskolleg  -  Fotovoltaik  -  & mehr  -  Kontakt: e-Mail,  News-Abo, Gästebuch, Impressum  -  Grußkarten, site map, Download und Suche

Diese Seite ist Teil des Webangebots http://www.r-krell.de. Sie können diese Seite per e-Mail weiter empfehlen (tell a friend).