Logo des digitalen Schulbuchs inf-schule.de. Schriftzug in Zustandsübergangsdiagramm eines endlichen Automaten.

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.

Sequenzdiagramm

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.

Sequenzdiagramm
X

Fehler melden

X

Suche