Beispiel: (Strukturerhaltung) Begriffsklärung: (AVL

Werbung
Durch zusätzliche Anforderungen bzgl. einer
Verteilung der Blätter und Höhen in Unterbäumen
kann man ein Degenerieren verhindern; Aspekte:
42
- Vorteil: geringer Aufwand für Grundoperationen
kann zugesichert werden.
17
52
- Nachteil: Strukturinvariante muss erhalten werden.
10
- Kosten der Strukturerhaltung?
22
45
57
Wegen der Balancierungseigenschaft mussten
alle Knoten vertauscht werden.
Beispiel: (Strukturerhaltung)
Einfügen von 10 unter
Erhaltung von FastVollständigkeit
Adelson-Velskij und Landis schlugen folgende
Balancierungseigenschaft vor:
45
Begriffsklärung: (AVL-Baum)
22
17
31.01.2007
57
42
Ein binärer Suchbaum heißt AVL-ausgeglichen,
höhenbalanciert und ein AVL-Baum, wenn
für jeden Knoten K gilt:
Die Höhe des linken Unterbaums von K
unterscheidet sich von der Höhe des rechten
Unterbaums höchstens um 1.
52
© A. Poetzsch-Heffter, Universität Kaiserslautern
611
© A. Poetzsch-Heffter, Universität Kaiserslautern
31.01.2007
612
Vorgehen:
Rotationen auf Suchbäumen:
• Gestaltsanalyse von AVL-Bäumen
Eine Rotation ist ein lokale Reorganisation eines
Suchbaums, bei der die Suchbaumeigenschaft
erhalten bleibt, die Höhen der Unterbäume aber
ggf. verändert werden.
• Rotationen auf Suchbäumen
• Datenstruktur für AVL-Bäume
• Heraussuchen
Rotation nach rechts (nach links entsprechend):
• Balancieren nach Einfügen
• Diskussion
Y
X
Gestaltsanalyse:
X
Y
Frage: Hat jeder AVL-Baum logarithmische Höhe?
C
(Dies ist die Voraussetzung, alle Grundoperationen
mit logarithmischen Aufwand realisieren zu können.)
A
Lemma:
B
A
B
C
Es gilt:
Für die Höhe eines AVL-Baums mit N Knoten gilt:
- alle Schlüssel aus A sind echt kleiner als X
h ≤ 2 * log (N +1) + 1
- alle Schlüssel aus B sind echt größer als X
und echt kleiner als Y
Beweis:
- alle Schlüssel aus C sind echt größer als Y
siehe Vorlesung
- X<Y
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
613
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
614
Doppelrotation links-rechts (rechts-links entsprechend):
Z
Ein Dictionary wird repräsentiert durch ein Objekt,
das eine Referenz auf einen Binärbaum enthält:
X
Y
Y
D
• Der leere Binärbaum wird durch die null-Referenz
repräsentiert (leeres Dictionary).
Z
X
A
A
B
B
C
D
C
Auch bei der Doppelrotation bleibt die SuchbaumEigenschaft erhalten.
• Jeder Knoten eines nichtleeren Binärbaums wird
durch ein Objekt vom Typ AVLNode repräsentiert
mit Instanzvariablen für:
- den Schlüssel
- die Daten
- die Referenz auf das linke Kind
- die Referenz auf das rechte Kind
- die Höhendifferenz bf
• Die Baumknoten sind gekapselt und können von
außen nur indirekt über die Grundoperationen
manipuliert werden.
Datenstruktur für AVL Bäume:
Zur Realisierung von AVL-Bäumen gehen wir von der
Implementierung der natürlichen Suchbäume aus:
Datenstruktur-Invarianten:
- Die Baumknoten bekommen ein zusätzliches Attribut
bf (balance factor), in dem die Höhendifferenz von
linkem und rechtem Unterbaum gespeichert wird.
- Die Binärbäume sind Suchbäume.
- Schlüssel kommen nicht doppelt vor.
- Die Höhendifferenz ist korrekt belegt.
- Die Operationen zum Einfügen und Löschen müssen
angepasst werden (ggf. wird dazu auch die Datenstruktur erweitert).
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
615
class AVLTreeDictionary
implements Dictionary
{
private AVLNode root;
616
- Ist der Wurzelschlüssel gleich dem gesuchten
Schlüssel, terminiert das Verfahren.
- Ist der Wurzelschlüssel größer als der gesuchte
Schlüssel, suche im linken Unterbaum weiter.
- Ist der Wurzelschlüssel kleiner als der gesuchte
Schlüssel, suche im rechten Unterbaum weiter.
private AVLNode( int k, Object d ) {
key = k;
data = d;
}
}
public AVLTreeDictionary() {
root = null; // leeres Dictionary
}
public Object get( int key ) {...}
private AVLNode searchNode( AVLNode current,
int key) {...}
public void put(int key, Object value){...}
private AVLNode insertNode( AVLNode current,
int key, Object v ){...}
private AVLNode rotate(AVLNode current){...}
public void remove( int key ) {
throw RuntimeException("not available");
}
}
© A. Poetzsch-Heffter, Universität Kaiserslautern
© A. Poetzsch-Heffter, Universität Kaiserslautern
Heraussuchen:
private static class AVLNode {
private int key;
private Object data;
private AVLNode left, right;
private int bf;
31.01.2007
31.01.2007
617
public Object get( int key ) {
AVLNode tn = searchNode(root,key);
if( tn == null ) {
return null;
} else {
return tn.data;
}
}
private AVLNode searchNode(
AVLNode current, int key)
{
if( current!=null && key != current.key ) {
if( current.key > key ) {
return searchNode( current.left, key );
} else { // current.key < key
return searchNode( current.right, key );
}
}
return current;
}
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
618
Einfügen:
Beispiel:
Entwicklung des Algorithmus in 4 Schritten:
Einfügen von 33:
45
1. Einfügen ohne Aktualisieren von bf und Rotation
2. Aktualisieren von bf, aber ohne Rotation
22
57
3. Aktualisieren von bf mit Aufruf der Rotation
4. Rotation mit Aktualisieren von bf an den
rotierten Knoten
17
42
33
1. Einfügen ohne Aktualisieren von bf und Rotation :
52
65
49
- Neue Knoten werden immer als Blätter eingefügt.
- Die Position des Blattes wird durch den Schlüssel
des neuen Knotens festgelegt.
class AVLTreeDictionary implements Dictionary
{
...
private AVLNode root;
...
public void put( int key, Object value ){
if( root == null ) {
root = new AVLNode(key, value);
} else {
AVLNode res = insertNode(root,key,value);
if( res!=null ) root = res;
}
}
...
}
- Beim Aufbau eines Baumes ergibt der erste
Knoten die Wurzel.
- Ein Knoten wird in den linken Unterbaum der
Wurzel eingefügt, wenn sein Schlüssel kleiner
ist als der Schlüssel der Wurzel; in den rechten,
wenn er größer. Dieses Verfahren wird rekursiv
fortgesetzt, bis die Einfügeposition bestimmt ist.
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
619
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
620
Rekursives Aufsuchen der Einfügestelle und Einfügen:
2. Aktualisieren von bf, aber ohne Rotation
void insertNode(
AVLNode current, int key, Object v ) {
// pre: current != null
//
if( key < current.key ) {
if ( current.left == null ) {
current.left = new AVLNode(key,v);
} else {
insertNode( current.left, key, v);
}
} else if( key > current.key ) {
if ( current.right == null ) {
current.right = new AVLNode(key,v);
} else {
insertNode( current.right, key, v);
}
} else { // key == current.key
current.data = v;
}
}
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
621
Algorithmisches Vorgehen:
- Einfügen wie oben.
- Aktualisieren von bf soweit nötig: Der Höhendifferenz
bf kann sich nur bei Knoten ändern, die auf dem Pfad
von der Wurzel zum eingefügten Knoten liegen.
Nur an diesen Knoten kann die AVL-Eigenschaft
verletzt werden.
- Bestimmen des kritischen Knotens KK; das ist der
nächste Elternknoten zum eingefügten Knoten mit
bf=2.
- Rotiere bei KK, Rotationstyp ergibt sich aus Pfad
von eingefügtem Knoten zu KK.
Wir werden zeigen, dass durch die Rotation der
Unterbaum mit Wurzel KK die gleiche Höhe erhält,
die er vor dem Einfügen hatte. Die Balancierungsfaktoren an Knoten oberhalb von KK brauchen also
nicht aktualisiert zu werden.
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
622
Bemerkung:
Einfügen mit Aktualisieren von bf:
private boolean insertNode
( AVLNode current, int key, Object v ){
/* pre: current != null
ens: result==true, wenn
h(current) > old(h(current))
result==false, sonst
*/
if( key < current.key ) {
if( current.left == null ) {
current.left = new AVLNode(key,v);
current.bf++;
// |current.bf| < 2
return (current.bf>0);
} else {
if( insertNode(current.left,key,v) ){
current.bf++;
return (current.bf>0);
} else {
return false;
}
}
} else if( key > current.key ) {
... // symmetrisch auf rechter Seite
} else { // key == current.key
current.data = v;
return false;
}
}
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
623
private AVLNode insertNode
( AVLNode current, int key, Object v )
{
if( key < current.key ) {
if( current.left == null ) {
current.left = new AVLNode(key,v);
current.bf++;
// |current.bf| < 2
return (current.bf>0) ? null : current;
} else {
AVLNode res =
insertNode(current.left,key,v);
if( res == null ) {
current.bf++;
if( current.bf < 2 ) {
return (current.bf>0)?null:current;
} else {
return rotate( current );
}
} else {
current.left = res;
return current;
}
}
} else if( key > current.key ) {
... // symmetrisch auf rechter Seite
} else { // key == current.key
current.data = v;
return current;
}
}
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
625
• Die obige Fassung veranschaulicht die Vorgehensweise, auf dem „Rückweg“ von einem rekursiven
Abstieg Operationen auszuführen.
• Die obige Fassung wird so nicht benötigt, da
bf nur bis zu kritischen Knoten zu aktualisieren ist.
Ist der kritische Knoten gefunden, wird rotiert und
damit die Aktualisierungen oberhalb unnötig.
3. Einfügen mit Aktualisieren von bf und Rotation:
Problem: Die Rotation macht es nötig, den Elternknoten
des kritischen Knotens zu modifizieren.
Idee: Statt true/false liefert die Einfüge-Operation:
- null, wenn sich die Höhe geändert hat.
- Die Referenz auf den möglicherweise rotierten
Unterbaum, wenn sich die Höhe nicht geändert hat;
deshalb ist ggf. root zu modifizieren:
public void put( int key, Object val ) {
if( root == null ) {
root = new AVLNode(key, value);
} else {
AVLNode res = insertNode(root,key,val);
if( res!=null ) {
root = res;
}
} }
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
624
4. Rotation mit Aktualisieren von bf:
Wir betrachten die Situation, dass bf im kritischen
Knoten KK auf 2 gestiegen ist, also links eingefügt
wurde. Die Unterbäume von KK bezeichnen wir mit
li, li.li, li.re etc.
Da h(li) = h(re)+2, kann der Wurzelknoten von li
nicht der neu eingefügte Knoten KN sein.
Es gibt vier unterschiedliche Fälle:
- KN ist in li.li eingefügt
(rechts-Rotation)
- KN ist in li.re eingefügt; bf ist abhängig davon, ob
KN neue Wurzel von li.re (links-rechts-Rotation)
KN in li.re.li eingefügt
(links-rechts-Rotation)
KN in li.re.re eingefügt
(links-rechts-Rotation)
Fall: links-links
h+2
vor Einfügen:
Y
h+1
X
0
1
h
C
h
h
A
31.01.2007
B
© A. Poetzsch-Heffter, Universität Kaiserslautern
626
nach Einfügen:
Beachte:
h+3
Y
h+2
X
2
Die Höhe nach der Rotation ist gleich der Höhe
vor dem Einfügen. Damit wird die AVLEigenschaft der Baumteile oberhalb des
kritischen Knotens nicht beeinflusst.
h
1
C
h+1
h
A
B
Fall: links-rechts:
vor Einfügen:
nach Rotation:
h+2
X
h+1
1
0
h+1 Y
0
0
Y
h+2
1
Z
h+1
0
1
Z
h
0
Y
A
D
h
h
h
B
h
X
C
0
A
h-1
h-1
B
C
(der Fall rechts-rechts geht analog)
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
627
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
nach Einfügen
links-rechts-links:
nach Einfügen
links-rechts:
2
h+3
h+2
2
Z
2
Z
h
-1
Y
628
D
1
h
-1
Y
h+1
X
1
A
0
X
h
0
h-1
B
C
nach Rotation:
nach Rotation:
h+2
1
X
h+1
0
Y
0
X
0
0
0
Z
Y
0
h+1
Z
-1
0
h
h
A
h-1
B
C
h
D
(der Fall rechts-links geht analog)
(der Fall rechts-links-rechts geht analog)
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
629
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
630
nach Einfügen
links-rechts-rechts:
h+3
h+2
2
Z
h
-1
Y
private AVLNode rotate( AVLNode current ) {
// pre: current != null && |current.bf| == 2
//
if( current.bf == 2 ) {
AVLNode cleft = current.left;
if( cleft.bf == 1 ) { // Variante LL
current.left = cleft.right;
current.bf
= 0;
cleft.right = current;
cleft.bf
= 0;
return cleft;
} else { // LR-Varianten
AVLNode clright = cleft.right;
current.left = clright.right;
cleft.right
= clright.left;
clright.left = cleft;
clright.right = current;
if( clright.bf == 1 ) { // LR(a)
current.bf
= -1;
cleft.bf
= 0;
} else if( clright.bf == -1 ) { // LR(b)
current.bf
= 0;
cleft.bf
= 1;
} else { // degenerierter Fall
current.bf
= 0;
cleft.bf
= 0;
}
clright.bf
= 0;
return clright;
}
} else { // current.bf == -2 )
... // symmmetrisch fuer rechts
}
D
h
h+1
X
-1
A
h-1
h
B
C
nach Rotation:
h+2
h+1
h
Y
1
h-1
A
B
X
0
h+1
Z
h
0
h
C
D
(der Fall rechts-links-links geht analog)
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
631
Diskussion:
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
632
Begriffsklärung: (Hashfunktion, -tabelle)
Beim Einfügen eines Knotens hat der rebalancierte Unterbaum stets die gleiche Höhe wie
vor dem Einfügen:
Seien
- S die Menge der möglichen Schlüsselwerte
(Schlüsselraum) und
- Der restliche Baum wird nicht beeinflusst.
- A die Menge von Adressen in einer Hashtabelle
(im Folgenden ist A immer die Indexmenge 0 .. m-1
eines Feldes).
- Höchstens eine Rotation wird benötigt.
Beim Löschen können ungünstigsten Falls so viele
Rotationen erforderlich sein, wie es Knoten auf
dem Pfad von der Löschposition bis zur Wurzel gibt.
A ordnet jedem Schlüssel
Eine Hashfunktion h: S
eine Adresse in der Hashtabelle zu.
Als Hashtabelle (HT) der Größe m bezeichnen wir
einen Speicherbereich, auf den über die Adressen
aus A mit konstantem Aufwand (unabhängig von m)
zugegriffen werden kann.
Da der Aufwand für eine Rotation aber konstant ist,
ergeben sich maximal O(log N) Operationen.
C. Hashing/Streuspeicherung
Anstatt durch schrittweises Vergleichen von
Schlüsseln auf einen Datensatz zuzugreifen,
versucht man bei Hash- oder Streuspeicherverfahren aus dem Schlüssel die
Positionsinformation des Datensatzes (z.B.
den Feldindex) zu berechnen.
Enthält S weniger Elemente als A, kann h injektiv sein:
Für alle s,t in S: s ≠ t => h(s) ≠ h(t)
d.h. die Hashfunktion ordnet jedem Schlüssel eine
eineindeutige Adresse zu. Dann ist perfektes Hashing
möglich.
Für viele praktisch relevante Szenarien erreicht
man dadurch Datenzugriff mit konstantem Aufwand.
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
Andernfalls können Kollisionen auftreten:
633
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
634
Klassifikation von Hashverfahren:
Hashverfahren unterscheiden sich
Begriffsklärung: (Kollision, Synonym)
• durch die Hashfunktion
Zwei Schlüssel s,t kollidieren bezüglich einer
Hashfunktion h, wenn h(s) = h(t).
• durch die Kollisionsauflösung:
- extern: Überläufer werden in Datenstrukturen
außerhalb der Hashtabelle gespeichert.
- offen: Überläufer werden an noch offenen
Positionen der Hashtabelle gespeichert.
Die Schlüssel s und t nennt man dann Synonyme.
Die Menge der Synonyme bezüglich einer Adresse a
aus A heißt die Kollisionsklasse von a.
Ist schon ein Datensatz mit Schlüssel s in der
Hashtabelle gespeichert, nennt man einen Datensatz
mit einem Synonym von s einen Überläufer.
• durch die Wahl der Größe von der Hashtabelle:
- statisch: Die Größe wird bei der Erzeugung
festgelegt und bleibt unverändert.
- dynamisch: Die Größe kann angepasst werden.
Wir betrachten im Folgenden eine Realisierung einer
statischen Hashtabelle mit externer Kollisionsauflösung durch ein Dictionary mit binärer Suche.
Anforderungen an Hashfunktionen:
Eine Hashfunktion soll
Hashfunktion:
- sich einfach und effizient berechnen lassen
(konstanter Aufwand bzgl. S)
Entscheidend ist, dass die Hashfunktion die Schlüssel
gut streut. Verbreitetes Verfahren:
- Wähle eine Primzahl als Hashtabellen-Größe.
- Wähle den ganzzahligen Divisionsrest als Hashwert:
- zu einer möglichst gleichmäßigen Belegung der
Hashtabelle führen
- möglichst wenige Kollisionen verursachen
private int hash( int key ) {
return Math.abs(key) % hashtable.length;
}
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
635
Schlecht wäre beispielsweise eine Wahl von m = 2i
als Tabellengröße bei dem Divisionsrest-Verfahren,
da bei Binärdarstellung der Schlüssel dann nur die
letzten i Bits relevant sind.
636
- Die Hashtabelle enthält den Datensatz zu s, wenn
hashtable[ h(s) ] == key
datatable[ h(s) ] != null
Die Daten liefert dann datatable[ h(s) ] .
Datenstruktur:
Wir realisieren eine Hashtabelle als Implementierung
der Schnittstelle Dictionary:
class HashDictionary implements Dictionary {
int[] hashtable;
Object[] datatable;
private Dictionary[] overflowtable;
public HashDictionary( int tabsize ) {
/* tabsize sollte eine Primzahl sein */
hashtable
= new int[tabsize];
datatable
= new Object[tabsize];
overflowtable = new Dictionary[tabsize];
}
private int hash( int key ) { ... }
public Object get( int key ) { ... }
public void put (int key,Object v ){ ... }
public void remove( int key ) { ... }
}
© A. Poetzsch-Heffter, Universität Kaiserslautern
© A. Poetzsch-Heffter, Universität Kaiserslautern
Sei s ein Schlüssel, h(s) sein Hashwert.
Der Datenstruktur HashDictionary liegen folgende
Invarianten zugrunde:
Bemerkung:
31.01.2007
31.01.2007
637
- Alle eingetragenen Elemente der Kollisionsklasse
zu h(s) befinden sich
in der Hashtabelle mit Index h(s) oder
in overflow[h(s)].
Wegen Löschens ist es möglich, dass die
Hashtabelle zu h(s) keinen Eintrag hat, sich trotzdem
aber Einträge in der Überlauftabelle zu h(s) befinden!
Löschen:
public void remove( int key ) {
int hix = hash(key);
if(
hashtable[hix] == key
&& datatable[hix] != null ) {
datatable[hix] = null;
} else if( overflowtable[hix] != null ) {
overflowtable[hix].remove(key);
}
}
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
638
Suchen:
Einfügen:
public Object get( int key ) {
int hix = hash(key);
if(
hashtable[hix] == key
&& datatable[hix] != null ) {
return datatable[hix];
} else if( overflowtable[hix] == null ) {
return null;
} else {
Object v = overflowtable[hix].get(key);
if( datatable[hix] == null ) {
datatable[hix] = v;
overflowtable[hix].remove(key);
}
return v;
}
}
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
public void put ( int key, Object value ) {
if( value != null ) {
int hix = hash(key);
if( datatable[hix] == null ) {
hashtable[hix] = key;
datatable[hix] = value;
} else if( hashtable[hix] == key ) {
datatable[hix] = value;
} else {
if( overflowtable[hix] == null ) {
overflowtable[hix] =
new BintreeDictionary();
}
overflowtable[hix].put(key,value);
}
}
}
639
© A. Poetzsch-Heffter, Universität Kaiserslautern
31.01.2007
640
Die Hashfunktion wird dabei benutzt, um komplexere
Schlüssel in einfachere, meist ganzzahlige Schlüssel
abzubilden.
Diskussion:
Die Komplexität der Operationen einer Hashtabelle
hängt ab von:
- der Hashfunktion und dem Füllungsgrad der Tabelle
- dem Verfahren zur Kollisionsauflösung
Bei guter Hashfunktion und kleinem Füllungsgrad
kommt man im Mittel mit konstantem Aufwand aus.
Da Injektivität meist nur annäherungsweise erreicht
werden kann, braucht man Kollisionsauflösung. Meist
verwendet man dazu offene Kollisionsauflösung.
Beispiel:
(Strings als Schlüssel)
Bemerkung:
In Bezeichnerumgebungen (vgl. ESSy 1, Folie 114)
und Deklarationstabellen von Übersetzern sind die
Schlüssel in natürlicherweise Zeichenreihen.
Hashverfahren sind ein Beispiel dafür, dass man sich
nicht immer für Algorithmen mit asymptotisch gutem
Verhalten interessiert.
Durch Hashing wird jedem Bezeichner eine natürliche
Zahl als Schlüssel zugeordnet. Unter diesem Zahlschlüssel wird die Deklarationsinformation verwaltet.
Anwendung von Hashverfahren:
Bemerkung:
Hashverfahren werden auch verwendet um Schlüsselräume zu vereinfachen. Wir betrachten hier
Anwendungen mit Zeichenreichen als Schlüssel.
Die praktische Bedeutung von Hashverfahren zur
Schlüsselvereinfachung wird auch durch die Methode
Zeichenreihen als Schlüssel:
- Vorteile:
bzgl. Anwendung der sich direkt ergebende
Schlüsseltyp; nicht längenbeschränkt.
/**
*
*
*
Returns a hash code value for the object.
This method is supported for the benefit
of hashtables such as those provided by
java.util.Hashtable ... */
public native int hashCode();
- Nachteile: Schlüsselvergleiche sehr teuer;
nicht zur Indizierung geeignet.
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
in der Java Klasse Object verdeutlicht.
641
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
642
Beobachtermuster:
Da über die Beobachter nichts bekannt ist,
realisiert man sie sinnvollerweise durch einen
Schnittstellentyp:
Als Beispiel einer Klasse, die Objekte nur über
deren Schnittstelle anspricht, betrachten wir eine
Anwendung des Beobachtermusters.
Aktie
interface Beobachter {
void steigen( Aktie a );
void fallen( Aktie a );
} // Fortsetzung nächste Folie
Beobachter
*
name: String
kurswert: int
*
void steigen(Aktie a)
Die Assoziation zwischen Aktien und Beobachtern
implementieren wir durch eine Liste in der Klasse
Aktie, die alle Beobachter der Klasse enthält:
void fallen(Aktie a)
Boersianer1
public class Aktie {
private String name;
private int kursWert;
private ArrayList beobachterListe;
Boersianer2
Aktie( String n, int anfangsWert ){
name = n;
kursWert = anfangsWert;
beobachterListe = new ArrayList();
}
Bei Realisierung der Klasse Aktie ist nur bekannt,
dass die Beobachter über das Steigen und Fallen
des Aktienkurses informiert werden wollen.
Wie Beobachter auf Änderungen reagieren, ist nicht
bekannt. Die Klassen können also getrennt entwickelt
werden.
public String getName(){
return name;
}
public int getKursWert(){
return kursWert;
}
// Fortsetzung nächste Folie
Bemerkung:
Das Beispiel illustriert eine Anwendung des
Entwurfmusters „Beobachter“ (engl. „observer“).
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
643
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
644
Zur Illustration von Beobachterimplementierungen
betrachten wir einen Boersianer der
Beim Setzen des Kurswertes werden auch die
Beobachter benachrichtigt:
- von der beobachteten Aktien kauft, wenn deren
Kurs unter 300 Euro fällt und er noch keine besitzt,
// Fortsetzung von voriger Folie
- verkauft, wenn der Kurs über 400 Euro steigt.
void setKursWert( int neuerWert ){
int alterWert = kursWert;
kursWert = neuerWert>0 ? neuerWert : 1 ;
public class Boersianer1 implements Beobachter{
private boolean besitzt = false;
ListIterator it =
beobachterListe.listIterator();
void fallen( Aktie a ) {
if( a.getKursWert() < 300 && !besitzt ) {
System.out.println("Kauf "+a.getName());
besitzt = true;
}
}
void steigen( Aktie a ) {
if( a.getKursWert() > 400 && besitzt ) {
System.out.print("Verkauf "+a.getName());
System.out.println();
besitzt = false;
}
}
if( kursWert > alterWert ) {
while( it.hasNext() ){
Beobachter b = (Beobachter)it.next();
b.steigen( this );
}
} else {
while( it.hasNext() ){
Beobachter b = (Beobachter)it.next();
b.fallen( this );
}
}
}
}
public
void anmeldenBeobachter( Beobachter b ) {
beobachterListe.add( b );
}
Anwendungsfragment:
...
Aktie vw = new Aktie("VW", 354);
Beobachter peter = new Boersianer1();
vw.anmeldeBeobachter( peter );
...
}
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
645
31.01.2007
© A. Poetzsch-Heffter, Universität Kaiserslautern
646
Herunterladen