Befrager-Architektur / Beobachter-Architektur
Das Datenmodell befragen
Wir betrachten zunächst noch einmal ein Programm zur Simulation der Ampel.
#------------------------------------------------------------------------------
# Datenmodell
#------------------------------------------------------------------------------
class Ampel(object):
def __init__(self, anfangszustand):
self.zustand = anfangszustand
def setZustand(self, z):
self.zustand = z
def getZustand(self):
return self.zustand
def schalten(self):
if self.zustand == 'rot':
self.zustand = 'rotgelb'
elif self.zustand == 'rotgelb':
self.zustand = 'gruen'
elif self.zustand == 'gruen':
self.zustand = 'gelb'
elif self.zustand == 'gelb':
self.zustand = 'rot'
def getLampen(self):
if self.zustand == 'rot':
lampen = (True, False, False)
elif self.zustand == 'rotgelb':
lampen = (True, True, False)
elif self.zustand == 'gruen':
lampen = (False, False, True)
elif self.zustand == 'gelb':
lampen = (False, True, False)
return lampen
#------------------------------------------------------------------------------
# GUI-Klasse
#------------------------------------------------------------------------------
from tkinter import *
# Farben
grau = '#404040'
rotAn = '#FF0000'
rotAus = '#550000'
gelbAn = '#FFFF00'
gelbAus = '#555500'
gruenAn = '#00FF00'
gruenAus = '#005500'
class GUI(object):
def __init__(self, datenmodell):
# Referenzattribute zum Datenmodell
self.ampel = datenmodell[0]
# Erzeugung des Fensters
self.fenster = Tk()
self.fenster.title("Ampel")
self.fenster.geometry("400x300")
# Zeichenfläche
self.canvas = Canvas(master=self.fenster)
self.canvas.place(x=0, y=0, width=400, height=300)
# Hintergrundbild
self.hintergrundbild = PhotoImage(file="hintergrund.gif")
self.canvas.create_image(0, 0, image=self.hintergrundbild, anchor=NW)
# Ampelanzeige
# Ampelkasten
self.canvas.create_rectangle(250, 120, 262, 152, fill=grau)
# Rot-Licht
self.id_rot = self.canvas.create_oval(252, 122, 260, 130, fill=grau)
# Gelb-Licht
self.id_gelb = self.canvas.create_oval(252, 132, 260, 140, fill=grau)
# Grün-Licht
self.id_gruen = self.canvas.create_oval(252, 142, 260, 150, fill=grau)
# Stange
self.canvas.create_rectangle(255, 152, 257, 184, fill=grau)
# Aktualisierung der Anzeige
self.anzeigeAktualisieren()
# Button zum Auswerten
self.buttonWeiter = Button(master=self.fenster,
text="weiter",
command=self.buttonWeiterClick)
self.buttonWeiter.place(x=150, y=270, width=100, height=20)
def buttonWeiterClick(self):
# Verarbeitung der Daten
self.ampel.schalten()
# Aktualisierung der Anzeige
self.anzeigeAktualisieren()
def anzeigeAktualisieren(self):
(lampeRot, lampeGelb, lampeGruen) = ampel.getLampen()
if lampeRot:
self.canvas.itemconfigure(self.id_rot, fill=rotAn)
else:
self.canvas.itemconfigure(self.id_rot, fill=rotAus)
if lampeGelb:
self.canvas.itemconfigure(self.id_gelb, fill=gelbAn)
else:
self.canvas.itemconfigure(self.id_gelb, fill=gelbAus)
if lampeGruen:
self.canvas.itemconfigure(self.id_gruen, fill=gruenAn)
else:
self.canvas.itemconfigure(self.id_gruen, fill=gruenAus)
#------------------------------------------------------------------------------
# Erzeugung der Objekte
#------------------------------------------------------------------------------
ampel = Ampel('rot')
datenmodell = [ampel]
gui = GUI(datenmodell)
gui.fenster.mainloop()
Das Objekt gui
nutzt hier eine Strategie, die man Befragen
oder Polling nennt.
Das Objekt gui
weiß
genau, wann sich das Datenmodell ändert (nach jedem Anklicken
einer Schaltfläche) und besorgt sich
dann die geänderten Daten zur Anzeige
auf den vorgesehenen Labels.
Aufgabe 1
Erkläre das Verhalten der Objekte anhand des folgenden Sequenzdiagramms.
Das Datenmodell beobachten
In der folgenden Implementierung wird eine andere Strategie benutzt,
um veränderte Daten an das Objekt gui
weiterzugeben.
#------------------------------------------------------------------------------
# Datenmodell
#------------------------------------------------------------------------------
class Ampel(object):
def __init__(self, anfangszustand):
self.zustand = anfangszustand
self.beobachter = None
def setBeobachter(self, pBeobachter):
self.beobachter = pBeobachter
def setZustand(self, z):
self.zustand = z
self.beobachter.anzeigeAktualisieren()
def getZustand(self):
return self.zustand
def schalten(self):
if self.zustand == 'rot':
self.zustand = 'rotgelb'
elif self.zustand == 'rotgelb':
self.zustand = 'gruen'
elif self.zustand == 'gruen':
self.zustand = 'gelb'
elif self.zustand == 'gelb':
self.zustand = 'rot'
self.beobachter.anzeigeAktualisieren()
def getLampen(self):
if self.zustand == 'rot':
lampen = (True, False, False)
elif self.zustand == 'rotgelb':
lampen = (True, True, False)
elif self.zustand == 'gruen':
lampen = (False, False, True)
elif self.zustand == 'gelb':
lampen = (False, True, False)
return lampen
#------------------------------------------------------------------------------
# GUI-Klasse
#------------------------------------------------------------------------------
from tkinter import *
# Farben
grau = '#404040'
rotAn = '#FF0000'
rotAus = '#550000'
gelbAn = '#FFFF00'
gelbAus = '#555500'
gruenAn = '#00FF00'
gruenAus = '#005500'
class GUI(object):
def __init__(self, datenmodell):
# Referenzattribute zum Datenmodell
self.ampel = datenmodell[0]
# Erzeugung des Fensters
self.fenster = Tk()
self.fenster.title("Ampel")
self.fenster.geometry("400x300")
# Zeichenfläche
self.canvas = Canvas(master=self.fenster)
self.canvas.place(x=0, y=0, width=400, height=300)
# Hintergrundbild
self.hintergrundbild = PhotoImage(file="hintergrund.gif")
self.canvas.create_image(0, 0, image=self.hintergrundbild, anchor=NW)
# Ampelanzeige
# Ampelkasten
self.canvas.create_rectangle(250, 120, 262, 152, fill=grau)
# Rot-Licht
self.id_rot = self.canvas.create_oval(252, 122, 260, 130, fill=grau)
# Gelb-Licht
self.id_gelb = self.canvas.create_oval(252, 132, 260, 140, fill=grau)
# Grün-Licht
self.id_gruen = self.canvas.create_oval(252, 142, 260, 150, fill=grau)
# Stange
self.canvas.create_rectangle(255, 152, 257, 184, fill=grau)
# Button zum Auswerten
self.buttonWeiter = Button(master=self.fenster,
text="weiter",
command=self.buttonWeiterClick)
self.buttonWeiter.place(x=150, y=270, width=100, height=20)
def buttonWeiterClick(self):
# Verarbeitung der Daten
self.ampel.schalten()
def anzeigeAktualisieren(self):
(lampeRot, lampeGelb, lampeGruen) = ampel.getLampen()
if lampeRot:
self.canvas.itemconfigure(self.id_rot, fill=rotAn)
else:
self.canvas.itemconfigure(self.id_rot, fill=rotAus)
if lampeGelb:
self.canvas.itemconfigure(self.id_gelb, fill=gelbAn)
else:
self.canvas.itemconfigure(self.id_gelb, fill=gelbAus)
if lampeGruen:
self.canvas.itemconfigure(self.id_gruen, fill=gruenAn)
else:
self.canvas.itemconfigure(self.id_gruen, fill=gruenAus)
#-----------------------------------------------------------
# Datenmodell
#-----------------------------------------------------------
ampel = Ampel(None)
datenmodell = [ampel]
#-----------------------------------------------------------
# GUI-Objekt
#-----------------------------------------------------------
gui = GUI(datenmodell)
# Beobachter festlegen
ampel.setBeobachter(gui)
ampel.setZustand('rot')
# Ereignisschleife starten
gui.fenster.mainloop()
Das Objekt gui
nutzt hier eine Strategie, die man Beobachten nennt.
Das Objekt gui
wird als registriertes beobachtendes Objekt des Datenmodell-Objekts
jeweils benachrichtigt, wenn sich das Datenmodell verändert hat.
Hierzu ist die Klasse Ampel
um ein Attribut beobachter
erweitert worden. Dieses Referenzattribut verwaltet zur Laufzeit
einen Zeiger auf ein GUI
-Objekt. Wenn sich Daten im Ampel
-Objekt verändern,
so wird das GUI
-Objekt angewiesen, die Anzeige zu aktualisieren.
Beachte, dass die Klassen Ampel
und GUI
so gestaltet sind, dass
die Trennung zwischen Datenmodell und GUI weiterhin Bestand hat.
Erst zur Laufzeit wird der Beobachter
des Datenmodellobjekts festgelegt.
Bei der Modellierung wird ein Beobachter mit Hilfe eines Referenzattributs vorgesehen, aber noch
nicht konkretisiert.
Aufgabe 2
Erkläre das Verhalten der Objekte anhand des folgenden Sequenzdiagramms.