VL15-Abfertigung

Werbung
Angemessene Reaktion:
Abfertigung

 Prinzip der Abfertigung
 Regeln gegen die Komplexität
 Symbolische Konstanten
Abfertigung über Arrays
Polymorphe Afertigung
Callback
Grundstruktur Abfertigung
(Dispatcher)
• Praktisch jedes Programm hat eine zentrale Abfertigung
("Eingangskontrolle")
• Grundstruktur:
if (bedingung1) aktion1();
else if (bedingung2) aktion2();
else if (bedingung3) ....
• Oft gibt es tiefer im Programm noch weitere solche Abfertiger.
• Abfertiger sind zentral für das Verständnis des Programms.
• Die Struktur ist oft nicht so einfach:
– schwierige kombinierte Bedingungen
– mehrstufige Fallunterscheidungen
Abfertigung
Metapher Schalterhalle:
• Anstellen immer an der kürzesten Schlange
• Abfertigungsschalter mit unterschiedlichen
Kompetenzen: D, EU, Int (je 2 D+EU, EU+Int)
• Abfertigungsstrategie:
Reihum aus Schlangen entnehmen. Falls freie
Kompetenz nicht passt, nächste Schlange.
Gerecht???
• Anstellen nach Nationalitäten
• Entnahme aus der Schlange mit der passenden
Nationalität, möglichst im Wechsel.
Kontext
class Zentrale {
List <Queue<Person>> halle = new List<>();
List <Schalter> schalter = new List<>();
Schalter aktuellerSchalter;
int next;
...
}
class Schalter extends Queue<Person> {
public Kompetenz kompetenz;
public static boolean passt(Nationalitaet,
Kompetenz) {...}
...
}
Abfertigungsstrategie
public void dispatch() {
for (int i=0: i<halle.size(); i++)
{ if (halle.get(next).size()!=0) break;
next=(next+1)%halle.size();
}
if (halle.get(next).size()==0) return;
if (Schalter.passt
(halle.get(next).peek().nationalitaet,
aktuellerSchalter.kompetenz))
aktuellerSchalter.add(halle.get(next)
.remove());
next=(next+1)%halle.size();
Wer versteht
das??
}
Break und Continue
• break
– verlässt den innersten switch-, while- oder
for-Block
– die entsprechende Schleife wird beendet
• continue
– verlässt den aktuellen while- oder for-Block
– die Schleife wird mit dem nächsten
Durchgang fortgesetzt.
Übersicht bitte!
public void dispatch() {
int next = naechsteNichtleereSchlange();
if (istLeer(next)) return;
if (aktuellerSchalterIstZustaendig())
entnimmUndBearbeite();
next = (next+1)%halle.size();
}
private boolean aktuellerSchalterIstZustaendig() {
return Schalter.passt
(halle.get(next).peek().nationalitaet,
aktuellerSchalter.kompetenz);
}
private void entnimmUndBearbeite() {
aktuellerSchalter.add(halle.get(next).remove());
Abfertigung als
Exception-Zentrale
• Abfertiger sind typische Exception-Zentralen:
– Prüfen der Verteilungskriterien –ggf. Exception werfen
– Fangen und Behandeln der Exceptions aus den Aktionen
• Grundstruktur:
if (bedingung1) {
try { aktion1(); }
catch (ActionException e)
{ /* Rep. oder Meldg. */ }
}else if (bedingung2)
try { aktion2(); }
catch (ActionException e)
{ /* Rep. oder Meldg. */ }
}else throw new DispatchException();
Verständnis-Alarm
• Abweichungen von der Grundstruktur sind die Regel:
• Nicht-triviale Bedingungen,
vielfach kombiniert
und ineinander verschachtelt
• verwirrend  häufige Fehlerquellen!
• Abhilfe durch Programmierstil!
Zur Illustration genügt ein "harmloses" Beispiel...
Beispiel Taschenrechner
Rechenwerk
verarbeiteEingabe (char c) : String
je nach Eingabe muss
unterschiedlich verfahren
werden: Dispatch!
Rechenwerk
verarbeiteEingabe (char c) : String
public void act() {
char eingabe = RechnerGUI.naechsterButton();
verarbeiteEingabe(eingabe);
}
private verarbeiteEingabe(char eingabe) {
// ziffer
 anhängen und anzeigen
// operator  letzten operator ausführen;
//
zahl und operator speichern.
// etc.
}
class Rechenwerk{
double zahl0 = 0;
String zahl1 = "";
char op = '=';
boolean bruch=false;
public String verarbeiteEingabe (char c) {
if (c>='0'&&c<='9'){ zahl1+=c; return zahl1; }
else { if (c=='.') {
if (!bruch) { bruch = true; zahl1+=c; return zahl1;}
else return "error – more than one decimal point";
}
else { if (c=='=' || ( c>=42 && c<=47) ) {
switch (op) {
case '+': zahl0 +=
Double.parseDouble(zahl1);
zahl1=""; break;
case '-': zahl0 -=
Double.parseDouble(zahl1);
vielleicht richtig
zahl1=""; break; //etc...
(??)
case '=': zahl0 =
aber
Double.parseDouble(zahl1);
grottenschlecht
zahl1=""; break;
programmiert!
default: return "error – illegal operator";
}
op = c;
} else return "error – invalid input";
}
}
Rechenwerk mit Dispatch
Regeln gegen die Komplexität
1.
Trenne Fallunterscheidung und Aktion!
2.
Benenne die Aktionen durch Methoden!
Rechnerstatus
abf ertigen(char eingabe)
3.
Rechenwerk
zif f er( )
operator( )
gleich( )
v orzeichen( )
dezimalpunkt( )
Benenne die Fälle durch boolesche Funktionen!
private boolean istZiffer(char c);
private boolean istOperator(char c);
// etc.
4. Verwende kaskadiertes if!
• statt:
if (a) aa;
else /* also !a */
{ if (b) bb;
else /* also !a & !b */
{ if (c) cc;
else /* also !a & !b & !c */ dd;
}
}
"rechts-verzweigt"
• einfacher:
a
if (a) aa;
else if (b) bb;
b
aa
else if (c) cc;
else dd;
c
bb
(analog switch)
cc
dd
Komplexitäts-reduzierte Abfertigung
/** Bearbeitung eines Eingabezeiches:
* Weiterleitung an die Rechenwerk-Methoden
* Grundidee:
*
ziffer  anhängen und Anzeigewert liefern
*
operator  letzten operator ausführen;
*
zahl und operator speichern.
*
Anzeigewert liefern
*/
public String abfertigen(char eingabe) {
if (istZiffer(eingabe)) return werk.ziffer(eingabe);
else if (istOperator(eingabe))
return werk.operator(eingabe);
else if (istDezimalpunkt(eingabe))
return werk.dezimalpunkt(eingabe);
else if (istGleich(eingabe))
return werk.gleich(eingabe);
else return fehler(eingabe);
}
Vorsicht bei Seiteneffekten!
• Fallunterscheidung durch boolesche Funktionen
(oder Ausdrücke)
• Seiteneffekt: die Abfragereihenfolge ist nicht beliebig
(unlogisch!):
private boolean istZiffer() {
return (schlange.entnehmen()>='0'
&& schlange.entnehmen()<='9');
}
So etwas
passiert Ihnen
natürlich nicht...
// istOperator, istGleich entsprechend..
private abfertigen() {
if (istZiffer()) werk.ziffer(schlange.entnehmen());
else if (istOperator()) werk,operator(schlange.entnehmen());
// etc
}
Symbolische Konstanten
• Fallunterscheidung über eine kleine feste Menge von
Alternativen
• Darstellbar als Symbolische Konstanten
• Beispiel:
EtchASketch.GREEN
EtchASketch.BLUE
• Symbolische Konstanten sind static final und
typischerweise int-Werte (nicht notwendig)
• Seit Java5 gibt es dafür auch Enumerationstypen
Beispiele für Symbolische Konstanten
public static final int
PLUS_KNOPF = 10,
MINUS_KNOPF = 11,
MAL_KNOPF = 12;
java.awt.Color.GREY
java.awt.BorderLayout.CENTER
java.awt.Component.BOTTOM_ALIGNMENT
java.awt.Label.RIGHT
java.lang.Math.PI // "echte" Konstante
java.lang.Double.NaN
Grundsätzlich alle Literale benennen, außer 0 und 1!
Symbolische Konstanten
 können mit == verglichen werden
 Sollten nicht in arithmetischen Ausdrücken
vorkommen
 sind oft Bestandteil eines Interfaces
Ab Java5 gibt es Enumerationen als Typ mit
ähnlichen Eigenschaften, aber darüber sprechen wir
ein anderes Mal.
Fallunterscheidung mit Symbolischen
Konstanten
• Oft ist es sinnvoll, symbolischen Konstanten eine Textform
zuzuordnen (analog toString())
• Schreiben wir eine statische Methode dafür:
public static String toString(int knopfID) {
if (knopfID == Rechner.PLUS_KNOPF)return "+";
else if (knopfID == Rechner.Minus_KNOPF)return "-";
else if (knopfID == Rechner.MAL_KNOPF)return "*";
// ...
}
• Beschriftung der Knöpfe ist dann ganz einfach:
Button plusknopf =
new Button(Rechner.toString(Rechner.PLUS_KNOPF));
Button minusknopf =
new Button(Rechner.toString(Rechner.MINUS_KNOPF));
Fallunterscheidung Array-Indizes
• Wir können das klasseninterne Wissen über die intKodierung der Tasten nutzen, um die Textdorm in
einem Array zu speichern:
static String[] tastentext =
{ "0","1","2","3",
"4","5","6","7",
"8","9","+","-",...
};
hier wird das
Geheimnisprinzip
wichtig!
Dann können wir die symbolischen Konstanten
zur Indizierung nutzen:
Button achtButton =
new Button(Rechner.tastentext[Rechner.ACHT_KNOPF]);
Fallunterscheidung ohne Abfrage
• Die Indizierung macht die Abfertigung einer Taste
möglich, ohne dabei die Taste "anzusehen" – d.h.
Fallunterscheidung ohne Abfrage:
class Rechner {
class
Rechner
public
void {
tasteAusgeben(int tastenID) {
switch (tastenID) {
public void
tastenID) {
case tasteAusgeben(int
PLUS_KNOPF:
ausgeben(tastentext[tastenID])
ausgeben(tastentext[PLUS_KNOPF]); break;
}
case MINUS_KNOPF:
}
ausgeben(tastentext[MINUS_KNOPF]); break;
// ...
}
}
}
"Übertragbares Verhalten"
 Weiter im BeispielTaschenrechner:
 Jeder Taste ist eine Reaktion zugeordnet
 Bisher kennen wir Verteiler über if, switch
oder Array
 Neuer Ansatz: Verhaltenskapsel
 Objekte "wissen" selbst, wie sie sich verhalten
sollen
 Verhalten kann Objekten zu Laufzeit zugeordnet
werden – gekapselt in ein Objekt.
Polymorphe Abfertigung
Interface Tastenbehandler mit drei
Implementierungen:
– Zifferntaste
– Operatortaste
– Cleartaste
Gemeinsame Methode
tasteGedrueckt()
Fallunterschiedung über Typen
if (taste instanceOf Zifferntaste)
((Zifferntaste)taste).tasteGedrueckt();
else if (taste instanceOf Operatortaste)
((Operatortaste)taste).tasteGedrueckt();
else if ((taste instanceOf Cleartaste)
((Cleartaste)taste).tasteGedrueckt();
else /* kann nicht sein */;
Polymorphie ersetzt die Fallunterscheidung
taste.tasteGedrueckt();
Taschenrechner – mit gekapseltem
Verhalten
gekapseltes
Verhalten
liesNaechsteTaste()
Rechner
tasteGedrueckt
(AnimateObject)
Tastenbehandler
RechnerGui
Ziffern
-taste
Operatortaste
Cleartaste
Rechner -Überblick
 Der Rechner macht immer dasselbe:
– Taste von der Gui lesen
– Taste verarbeiten durch Rufen der Methode tasteGedrueckt()
eines Tastenbehandlers
 Trotzdem passiert Unterschiedliches:
– verschiedene Tastenbehandler implementieren
"tasteGedrueckt" verschieden  Polymorphie
 Tastenbehandler kapseln Verhalten
– Verhalten kann dynamisch zugeordnet werden
 Erforderlich: ein Interface
- als Vertrag über die Methodensignatur
Rechner – Gekapseltes Verhalten
interface Tastenbehandler {
tasteGedrueckt(Rechner rechner);
}
class Zifferntaste implements Tastenbehandler {
final int wert;
Zifferntaste(int wert) {
this.wert = wert;
}
void tasteGedrueckt(Rechner rechner) {
rechner.anhaengen(wert);
}
}
class Operatortaste implements Tastenbehandler
{ /* analog */ }
class Cleartaste implements Tastenbehandler
{ /* analog */ }
Rechner – Codegerüst:
Handler-Array initialisieren
class Rechner extends AnimateObject {
RechnerGUI gui = new RechnerGUI();
Tastenbehandler[] tasten = new Tastenbehandler[];
Rechner() {
// Dispatch-Array einrichten:
for (int i=0; i<10; i++)
tasten[i] = new Zifferntaste(i);
for (int i=Rechner.PLUS_TASTE;
i<Rechner.GLEICH_TASTE; i++)
tasten[i] = new Operatortaste(i);
tasten[Rechner.CLEAR_TASTE] = new ClearTaste();
}
Rechner – Codegerüst:
Polymorphe Bearbeitung
public class Rechner implements Animate {
private RechnerGUI gui;
private Tastenbehandler[] tasten;
void act() {
int tastencode = -1;
try { tastencode = gui.liesNaechsteTaste();
tasten[tastencode].tasteGedrueckt(this);
} catch (KeineTasteException e)
{ /* ignorieren */ }
}
}
polymorph!
Ein Standard-Interface
• Animate aus cs101 hat eine parameterlose
Methode: act()
• kann zur Verhaltenskapselung benutzt werden –
auch ohne Thread:
void bearbeite(Animate technik){
technik.act(); // polymorph!
}
• Ebenso eignet sich das Interface Runnable aus
java.lang – es definiert die Methode run():
void bearbeite(Runnable technik){
technik.run(); // polymorph!
}
Callback
liesNaechsteTaste()
Rechner
tasteGedrueckt(Rechner
r)
tasteGedrueckt
(AnimateObject)
Tastenbehandler
RechnerGui
anhaengen()
ausrechnen()
neueAufgabe()
Ziffern
-taste
void tasteGedrueckt(Rechner rechner)
{ rechner.anhaengen(wert); }
Operatortaste
Cleartaste
Callback
 Callback bedeutet, dass die gerufene
Methode ihren Aufrufer "zwischendurch"
kontaktieren will.
 Dazu muss sie den Aufrufer "kennen" – d.h.
eine Referenz auf ihn besitzen.
- Technik: Der Aufrufer gibt sie als Parameter mit.
 Sie muss aber auch den Typ des Aufrufers
kennen!
- Technik: oft implementiert der Aufrufer ein
Interface
... genug für diesmal!
Üben Sie Abfertigung in Nodenet
– das Callback-Prinzip brauchen
wir für GUIs

Herunterladen