Material OOP Grundlagen Delphi Software-Technik Bonsai Digitaltechnik Ereignisse Grafik UML Netze Fischertechnik Tipps Werkzeuge Literatur Automaten Sprachen Datenbanken XML Prolog Berechenbarkeit
Pfad: Startseite / Fächer / Informatik / Material
Autor: mk
08.02.2007 17:41
12549
Automatentheorie

Ein Getränkeautomat

- Beispiel für eine Delphi-Realisierung

Der zu realisierende Automat sei durch folgenden Graphen (aus Informatik heute, Bd.2, S.84ff) gegeben:

Automatengraph

Pfichtenheft

/1/ Das Programm soll objektorientiert nach dem MVC-Muster entwickelt werden.
/2/ Der Automat soll übersichtlich beschrieben werden.
/3/ Das Programm soll einfach an andere Automaten angepasst werden können.
/4/ Eingaben sollen nur aus dem Eingabe-Alphabet möglich sein.
/5/ Zu Kontrollzwecken soll der aktuelle Zustand änderbar sein.
/6/ Ein- und Ausgaben sollen protokolliert werden.

Prototyp

GUI

Objektorientierte Analyse und Design

Klassendiagramm

Implementierung

Model

Zunächst soll die TAutomat-Klasse besprochen werden. Wie man sieht, werden selbstdefinierte Aufzähltypen TZustand, TEingabe, TAusgabe verwendet. Diese Typen legen die Zustände, das Eingabe- und das Ausgabe-Alphabet fest. Die Übergangs- und die Ausgabefunktion sind ebenfalls außerhalb der Klasse in Array-Konstanten festgelegt.

const
  { 0. Automatenname eintragen }
  automatname = 'Getraenkeautomat, informatik heute, Bd.2, S.84ff';
type
  { 1. Zustandsmenge, Eingabe- und Ausgabealphabet anpassen                }
  TZustand = (z0,z50,z100,z150);
  TEingabe = (e50,e100,eK,eW);
  TAusgabe = (a50,a100,a150,aG,aN);
const
  { 2. Startzustand eintragen }
  startzustand = z0;
  { 3. Klartextentsprechungen eintragen                                    }
  zText : array[TZustand] of string =
          ('kein Geld','0,50 DM','1,00 DM','1,50 DM');
  eText : array[TEingabe] of string =
          ('50 Pf','1 DM','Korrekturtaste','Warenhebel');
  aText : array[TAusgabe] of string =
          ('50 Pf','1 DM','1 DM und 50 Pf','Getränk','nichts');
  { 4a. Automatentafel eingeben, auf Zeilen und Spalten achten              }
  fue   : array[TZustand,TEingabe] of TZustand =
          ((z50,z100,z0,z0),   { fue(z0,e50), fue(z0,e100), ...   }
           (z100,z150,z0,z50), { fue(z50,e50), fue(z50,e100), ... }
           (z150,z100,z0,z100),
           (z150,z100,z0,z0));
  { 4b. Automatentafel eingeben, auf Zeilen und Spalten achten              }
  fa    : array[TZustand,TEingabe] of TAusgabe =
          ((aN,aN,aN,aN),      { fa(z0,e50), fa(z0,e100), ...   }
           (aN,aN,a50,aN),     { fa(z1,e50), fa(z1,e100), ...   }
           (aN,a100,a100,aN),
           (a50,a100,a150,aG));

Damit ist die TAutomat-Klasse universell für alle Transduktoren. Zur Realisierung eines anderen Automaten sind lediglich die rot gekennzeichneten Stellen zu ändern, an der eigentlichen Automaten-Klasse ändert sich nichts.

TAutomat hält den aktuellen Zustand, die gemachte Eingabe und die letzte Ausgabe fest. Außer einfachen Set- und Get-Methoden und einem erweiterten Konstruktor gibt es nur die Methode verarbeiteEingabe, die auch verblüffend einfach ist:

constructor TAutomat.create;
begin
  inherited create;
  zustand := startzustand;
end;
...
procedure TAutomat.verarbeiteEingabe;
begin
  ausgabe := fa[zustand,eingabe];
  zustand := fue[zustand,eingabe];
end;

View/Control

Die grafische Benutzeroberfläche ist völlig unabhängig vom speziellen Automaten, dh. an der Unit uGUI muss nichts geändert werden. Perfekte Wiederverwertung!

combobox Interessant ist die Verwendung von ComboBoxen für Zustand, Eingabe und Ausgabe.
Die auswählbaren Items werden beim Programmstart aus den entsprechenden Arrays in die ComboBoxen eingelesen. Damit ist sichergestellt, dass für Eingaben und Zustände nur erlaubte Werte ausgewählt werden können. Die Auswahlliste bei der Ausgabe dient der Information über das Ausgabe-Alphabet, das hier eingesehen werden kann.
Details für Belegung und Auslesen der Auswahlisten/ComboBoxen finden sich in folgenden Quelltext-Auszügen.

...
// Eingabealphabet in ComboBox eintragen
  for e := Low(TEingabe) to High(TEingabe) do
    cbEingabe.Items.Add(eText[e]);
  cbEingabe.ItemIndex := 0;
...
procedure TForm1.bVerarbeiteClick(Sender: TObject);
begin
  if (cbZustand.ItemIndex <> -1) and (cbEingabe.ItemIndex <> -1)
  then
  begin
    automat.setZustand(TZustand(cbZustand.ItemIndex));
    automat.setEingabe(TEingabe(cbEingabe.ItemIndex));
    automat.verarbeiteEingabe;
    mEingaben.Lines.Add(eText[automat.getEingabe]);
    cbZustand.ItemIndex := Ord(automat.getZustand);
    cbAusgabe.ItemIndex := Ord(automat.getAusgabe);
    mAusgaben.Lines.Add(aText[automat.getAusgabe]);
  end;
end;

Download: getraenkeautomat.zip

Test

Durch die Möglichkeit, den Zustand zu setzen, kann man den Automaten bequem völlig durchtesten. Man geht Zustand für Zustand durch, setzt die entsprechenden Eingaben und kontrolliert den Folgezustand sowie die Ausgabe.

Fehleingaben, die man in den ComboBoxen versucht, werden durch Kontrollen auf den ItemIndex -1 (nicht ausgewählt) abgefangen.

Aufgabe

Realisiere folgenden Übungsautomaten mit obiger Vorlage.

ueb1.gif

frühere Realisierung des Automaten

Im folgenden Quelltext sind die für die Änderung auf einen anderen Automaten relevanten Stellen rot markiert:

unit uAuto;  { mk, 29.1.03 , endlicher Automat, ohne MVC, ohne OOP         }
             { Getränkeautomat aus Informatik heute, Bd.2, S.84ff          }
interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;
type
  { 1. Zustandsmenge, Eingabe- und Ausgabealphabet anpassen                }
  TZustand = (z0,z50,z100,z150);
  TEingabe = (e50,e100,eK,eW);
  TAusgabe = (a50,a100,a150,aG,aN);
const
  { 2. Klartextentsprechungen eintragen                                    }
  zText : array[TZustand] of string =
          ('kein Geld','0,50 DM','1,00 DM','1,50 DM');
  eText : array[TEingabe] of string =
          ('50 Pf','1 DM','Korrekturtaste','Warenhebel');
  aText : array[TAusgabe] of string =
          ('50 Pf','1 DM','1 DM und 50 Pf','Getränk','nichts');
  { 3a. Automatentafel eingeben, auf Zeilen und Spalten achten              }
  fue   : array[TZustand,TEingabe] of TZustand =
          ((z50,z100,z0,z0),   { fue(z0,e50), fue(z0,e100), ...   }
           (z100,z150,z0,z50), { fue(z50,e50), fue(z50,e100), ... }
           (z150,z100,z0,z100),
           (z150,z100,z0,z0));
  { 3b. Automatentafel eingeben, auf Zeilen und Spalten achten              }
  fa    : array[TZustand,TEingabe] of TAusgabe =
          ((aN,aN,aN,aN),      { fa(z0,e50), fa(z0,e100), ...   }
           (aN,aN,a50,aN),     { fa(z1,e50), fa(z1,e100), ...   }
           (aN,a100,a100,aN),
           (a50,a100,a150,aG));
type
  TForm1 = class(TForm)
  { 4. für jede Eingabe einen Button                                       }
    be100: TButton; be50: TButton; beK: TButton; beW: TButton;
    eZustand: TEdit; lZustand: TLabel; eAusgabe: TEdit; lAusgabe: TLabel;
    mEin: TMemo; lmEin: TLabel; mAus: TMemo; lmAus: TLabel; bEnde: TButton;
    procedure verarbeite_Eingabe(Sender: TObject);
    procedure bEndeClick(Sender: TObject);
  end;

var
  Form1   : TForm1;
  zustand : TZustand;                    { Zustand wird global gespeichert   }

implementation
{$R *.DFM}

procedure TForm1.verarbeite_Eingabe(Sender : TObject);
var                           { Achtung, mit allen ButtonClicks verbinden !! }
  eingabe : TEingabe;
  ausgabe : TAusgabe;
begin
  if Sender=be50  then eingabe := e50;  { 5. Button <--> Eingabe zuordnen    }
  if Sender=be100 then eingabe := e100; { 5. Button <--> Eingabe zuordnen    }
  if Sender=beK   then eingabe := eK;   { 5. Button <--> Eingabe zuordnen    }
  if Sender=beW   then eingabe := eW;   { 5. Button <--> Eingabe zuordnen    }
  Form1.mEin.Lines.Add(eText[eingabe]); { zugehörigen Text ausgeben          }
  ausgabe := fa[zustand,eingabe];       { Ausgabe bestimmen                  }
  eAusgabe.text := aText[ausgabe];      { zugehörigen Text ausgeben          }
  Form1.mAus.Lines.Add(aText[ausgabe]); { zugehörigen Text ausgeben          }
  zustand := fue[zustand,eingabe];      { neuen Zustand bestimmen            }
  eZustand.text := zText[zustand];      { zugehörigen Text ausgeben          }
end;

procedure TForm1.bEndeClick(Sender: TObject);
begin
  halt;
end;

initialization
  zustand := z0;                        { 6. Startzustand setzen             }
end.

Valid XHTML 1.0! lokal