Chair of Software Engineering Einführung in die Programmierung Prof. Dr. Bertrand Meyer Lecture 18:...
-
Upload
magdalena-schiessl -
Category
Documents
-
view
107 -
download
0
Transcript of Chair of Software Engineering Einführung in die Programmierung Prof. Dr. Bertrand Meyer Lecture 18:...
Chair of Software Engineering
Einführung in die Programmierung
Prof. Dr. Bertrand Meyer
Lecture 18: Undo/Redo
2
Weiterführende Referenzen
Kapitel 21 von Object-Oriented Software Construction, Prentice Hall, 1997
Erich Gamma et al., Design Patterns, Addison –Wesley, 1995: “Command pattern”
3
Die Problemstellung
Dem Benutzer eines interaktiven Systems die Möglichkeit geben, die letzte Aktion rückgängig zu machen.
Bekannt als “Control-Z”
Sollte mehrstufiges rückgängig Machen (“Control-Z”) und Wiederholen (“Control-Y”) ohne Limitierung unterstützen, ausser der Benutzer gibt eine maximale Tiefe an.
4
Warum machen wir das?
Undo/Redo sinnvoll in jeder interaktiven Anwendung Entwickeln Sie keine interaktiven
Anwendungen, ohne Undo/Redo zu implementieren
Nützliches Design-Pattern (“Command” Pattern)
Veranschaulicht die Verwendung von Algorithmen und Datastrukturen
Beispiel für O-O Techniken: Vererbung, Aufgeschobene Klassen, Polymorphe Datenstrukturen, Dynamisches Binden,…
Beispiel einer schönen und eleganten Lösung
5
In unserem Beispiel: Ein Texteditor
Begriff der „aktuellen Zeile“ mit folgenden Befehlen:
Löschen der aktuellen Zeile Ersetzen der aktuellen Zeile mit einer Anderen Einfügen einer Zeile vor der aktuellen Position Vertauschen der aktuellen Zeile mit der
Nächsten (falls vorhanden) „Globales Suchen und Ersetzen“ (fortan GSE):
Jedes Auftreten einer gewissen Zeichenkette durch eine andere ersetzen.
...
Der Einfachheit halber nutzen wir eine Zeilen-orientierte Ansicht, aber die Diskussion kann auch auf kompliziertere Ansichten angewendet werden.
6
Eine einfache Lösung
Sichern des gesamten Zustandes vor jeder Operation.
Im Beispiel: Der Text, der bearbeitet wird und die aktuelle Position im Text.
Wenn der Benutzer ein „Undo“ verlangt, stelle den zuletzt gesicherten Zustand wieder her.
Aber: Verschwendung von Ressourcen, insbesondere Speicherplatz.
Intuition: Sichere nur die Änderungen (‘diff’) zwischen zwei Zuständen.
7
Schlüsselschritt im Entwerfen einer Software-Architektur
Hier:
Der Begriff eines “Befehls”
Die richtigen Abstraktionen finden
(die interessanten Objekt-Typen)
8
Den „Verlauf“ einer Sitzung speichern
Die Verlauf-Liste:
verlauf : LIST [BEFEHL]
Löschen
Austauschen
Einfügen
Einfügen
Einfügen
alt Am Neusten
9
Was ist ein “Befehl” (Command) -Objekt?
Ein Befehl-Objekt beinhaltet genügend Informationen über eine Ausführung eines Befehls durch den Benutzer, um
den Befehl auszuführen den Befehl rückgängig zu machen
Beispiel: In einem “Löschen”-Objekt brauchen wir:
• Die Position der zu löschenden Zeile
• Der Inhalt dieser Zeile!
10
Allgemeiner Begriff eines Befehls
deferred
class
BEFEHL feature
execute -- Eine Ausführung des Befehls ausführen.
undo -- Eine frühere Ausführung des Befehls -- rückgängig machen
end
deferred
: doneend
deferredend
done: BOOLEAN -- Wurde dieser Befehl ausgeführt?
ensure already: done
require already: done
11
Die Befehl-Klassenhierarchie
ausführen*
rückgängig_machen*
…
ausführen+
rückgängig_machen+
linie: STRINGindex: INTEGER
...
ausführen+
rückgängig_machen+ index
...
*BEFEHL
+LÖSCHEN
+EINFÜGEN
* aufgeschoben
+ wirksam
12
Zugrundeliegende Klasse (Aus dem Geschäftsmodell)
class BEARBEITUNGS_CONTROLLER featuretext : LIST [STRING]position: LIST_ITERATOR [STRING]löschen
-- Lösche Zeile an aktueller Position.require
not position.offdo
position.removeend
rechts_einfügen (linie : STRING)-- Füge linie nach der aktuellen Position
ein.require
not position.afterdo
position.put_right (line)end
... Auch: item, index, go_ith, put_left ...end
13
Eine Befehlsklasse (Skizze, ohne Verträge)
class LÖSCHEN inherit BEFEHL featurecontroller : BEARBEITUNGS_CONTROLLER
-- Zugriff zum Geschäftsmodell.
linie : STRING-- Zu löschende Zeile.
index : INTEGER-- Position der zu löschenden Zeile.
ausführen-- Lösche aktuelle Zeile und speichere sie.
do linie := controller.item ; index := controller.indexcontroller.löschen ; done := True
end
rückgängig_machen-- Füge vorher gelöschte Zeile wieder ein.
do controller.go_i_th (index)controller.put_left (line)
endend
14
Die Verlauf-Liste
Eine polymorphe Datenstruktur
verlauf : LIST [BEFEHL]
Löschen
Austauschen
Einfügen
Einfügen
Einfügen
alt Am Neusten
15
Erinnerung: Liste von Figuren
bilder : LIST [FIGUR ]p1, p2 : POLYGONc1, c2 : KREISe : ELLIPSE
class LIST [G ] featureextend (v : G) do
… endlast : G…
end
bilder.extend (p1 ) ; bilder.extend (c1 ) ; bilder.extend (c2 )
bilder.extend (e ) ; bilder.extend (p2 )
(POLYGON) (KREIS) (POLYGON)(KREIS) (ELLIPSE)
16
Die Verlauf-Liste
Eine polymorphe Datenstruktur
verlauf : LIST [BEFEHL]
cursor: ITERATION_CURSOR [BEFEHL]
Löschen
Austauschen
Einfügen
Einfügen
Einfügen
alt Am Neustencursor
17
Einen Benutzerbefehl ausführenbenutzeranfrage_decodieren
if “Anfrage ist normaler Befehl” then“Erzeuge ein Befehlsobjekt c , der Anforderung
entsprechend”from until cursorlis_last loop cursorlremove_right endverlauflextend (c); cursorlforthclausführen
elseif “Anfrage ist UNDO” thenif not cursorlbefore then -- Ignoriere überschüssige
Anfragen cursorlitemlrückgängig_machen cursorlbackend
elseif “Anfrage ist REDO” thenif not cursorlis_last then –– Ignoriere überschüssige
Anfragen cursorlforth cursorlitemlausführen end
end
Pseudocode, siehe nächste Implementation
Löschen AustauschenEinfügen Einfügen
cursor
18
Bedingte Erzeugung (1)
…
…
a1 : A
if kondition_1 then -- “Erzeuge a1 als Instanz von B”
elseif kondition_2 then
-- “Erzeuge a1 als Instanz von C”
... etc.a1 : A; b1 : B ; c1 : C ; d1 : D ; ...if kondition_1 then
create b1.make (...)a1 := b1
elseif kondition_2 then
create c1.make (...)a1 := c1
... etc.
A
B C
D
19
Bedingte Erzeugung (2)
…
…
a1 : A
if kondition_1 then
create {B } a1.make (...)
elseif kondition_2 then
create {C } a1.make (...)
... etc.
A
B C
D
a1 : A
if kondition_1 then -- “Erzeuge a1 als Instanz von B”
elseif kondition_2 then
-- “Erzeuge a1 als Instanz von C”
... etc.
20
Einen Benutzerbefehl ausführenbenutzeranfrage_decodieren
if “Anfrage ist normaler Befehl” then“Erzeuge ein Befehlsobjekt c , der Anforderung
entsprechend”from until cursorlis_last loop cursorlremove_right endverlauflextend (c); cursorlforthclausführen
elseif “Anfrage ist UNDO” thenif not cursorlbefore then -- Ignoriere überschüssige
Anfragen cursorlitemlrückgängig_machen cursorlbackend
elseif “Anfrage ist REDO” thenif not cursorlis_last then –– Ignoriere überschüssige
Anfragen cursorlforth cursorlitemlausführen end
end
Löschen AustauschenEinfügen Einfügen
cursor
21
Befehlsobjekte erzeugen: Erster Ansatz
c : BEFEHL
...
benutzerbefehl_decodieren
if “Anfrage ist Löschen” then
create {LÖSCHEN} c
elseif “Anfrage ist Einfügen” then
create {EINFÜGEN} celse
etc.
22
Die Befehl-Klassenhierarchie
ausführen*
rückgängig_machen*
…
ausführen+
rückgängig_machen+
linie: STRINGindex: INTEGER
...
ausführen+
rückgängig_machen+ index
...
*BEFEHL
+LÖSCHEN
+EINFÜGEN
* aufgeschoben
+ wirksam
23
Befehlsobjekte erzeugen: Besserer Ansatz
Geben Sie jedem Befehls-Typ eineNummer
Füllen Sie zu Beginn einen Arraybefehle mit je einer Instanz jedesBefehls-Typen.
Um neue Befehlsobjekte zu erhalten:
“Bestimme befehls_typ”
c := (befehle [befehls_typ]).twin
Löschen
Einfügen
Austauschen
...
1
2
n
befehls_typ
Einen “Prototypen” duplizieren
befehle
24
Das Undo/Redo- (bzw. Command-) Pattern
Wurde extensiv genutzt (z.B. in EiffelStudio und anderen Eiffel-Tools).Ziemlich einfach zu implementieren.Details müssen genau betrachtet werden (z.B. lassen sich manche Befehle nicht rückgängig machen).Eleganter Gebrauch von O-O-Techniken
Nachteil: Explosion kleiner Klassen
25
Der Gebrauch von Agenten
Für jeden Benutzerbefehl haben wir zwei Routinen:
Die Routine, um ihn auszuführen Die Routine, um ihn rückgängig zu machen
26
Die Verlauf-Liste im Undo/Redo-Pattern
verlauf: LIST [BEFEHL]
Ältester Neuster
Löschung
Aus-tausch
Einfügung
Einfügung
27
Die Verlauf-Liste mit Agenten
Die Verlauf-Liste wird einfach zu einer Liste von Agentenpaaren:
verlauf: LIST [TUPLE
[ausführer : PROCEDURE [ANY, TUPLE];
rückgängig_macher : PROCEDURE [ANY, TUPLE]]
Das Grundschema bleibt dasselbe, aber man braucht nun keine Befehlsobjekte mehr; die Verlauf-Liste ist einfach eine Liste, die Agenten enthält.
Einfügung
Einfügung
Löschung
Austauschen
Einfügung
Entfernen
Entfernen
Wieder-Einfügun
g
Austauschen
Entfernen
Benanntes Tupel
28
Einen Benutzerbefehl ausführen (vorher)benutzeranfrage_decodieren
if “Anfrage ist normaler Befehl” then“Erzeuge ein Befehlsobjekt c , der Anforderung
entsprechend”from until cursorlis_last loop cursorlremove_right endverlauflextend (c); cursorlforthclausführen
elseif “Anfrage ist UNDO” thenif not cursorlbefore then -- Ignoriere überschüssige
Anfragen cursorlitemlrückgängig_machen cursorlbackend
elseif “Anfrage ist REDO” thenif not cursorlis_last then –– Ignoriere überschüssige
Anfragen cursorlforth cursorlitemlausführen end
end
Löschen Austauschen
Einfügen Einfügen
cursor
29
Einen Benutzerbefehl ausführen (jetzt)Einen Benutzerbefehl ausführenEinen Benutzerbefehl ausführen
“Dekodiere Benutzeranfrage mit zwei Agenten do_it and undo_it ”if “Anfrage ist normaler Befehl” then
from until cursorlis_last loop cursorlremove_right endverlauflextend ([do_it, undo_it ]); cursorlforthdo_itlcall ([])
elseif “Anfrage ist UNDO” thenif not cursorlbefore then -- Ignoriere überschüssige
Anfragen cursorlitemlausführerlcall ([]) cursorlbackend
elseif “Anfrage ist REDO” thenif not cursorlis_last then –– Ignoriere überschüssige
Anfragen cursorlforth cursorlitemlrückgängig_macherlcall ([]) end
end
Einfügung
Einfügung
Löschung Austausch
Entfernung Entfernung Wiedereinf.
Austausch
30
Was wir gesehen haben
Menschen machen Fehler!Auch wenn sie keine Fehler machen: sie wollen experimentieren. Undo/Redo unterstützt einen „trial and error“-Stil.
Undo/Redo-Pattern: Sehr nützlich in der Praxis Weit verbreitet Ziemlich einfach zu implementieren Exzellentes Beispiel von eleganten O-O-Techniken Mit Agenten noch besser!