Interaktives Tutorial mit Async auf Basis des MVC
Verfasst: Donnerstag 28. April 2016, 14:01
Hallo liebes Forum,
nachdem ich einigermaßen das Konzept Model-View-Controller (nach Trygve Reenskaug von 1979) verdaut habe, würde ich gerne zum besseren Verständnis ein kleines Programm schreiben.
Dieses soll mit Python 2.7.10 möglichst ohne Zusatzmodule auskommen. Also Module wie Asyncio in Python 3 stehen mir zu Lernzwecken ausdrücklich nicht zur Verfügung.
Ziel des Programms ist eine von View und Controller entkoppelte Abfrage der aktuellen Uhrzeit des Systems, welche natürlich im Model erfasst und bereitgehalten wird. Nach einem Statuswechsel des Model fragt das View die Daten automatisiert ab.
Die Ausgabe des View geht der Übersichtlichkeit halber zur Kommandozeile. Der Controller soll zunächst einmal nur das Programm beenden (Steuerung des View). Später sind auch Steuerungsmöglichkeiten des Model angedacht (z.B. Speicherung zweier Zeitpunkte und Darstellungsänderungen des View, dass genau diese Zeitpunkte dargestellt werden sollen).
Das Lösungskonzept dieser Aufgabe ist denkbar einfach:
Führe im Hintergrund eine Endlosschleife durch, die die Uhrzeit alle 0,1 Sekunden abfragt. Sobald sich die Uhrzeit ändert, soll dem View signalisiert werden, dass sich die Uhrzeit geändert hat und das View aktualisieren kann. Daraufhin macht das View das einzig sinnvolle in dieser Konstellation: Es fragt vom Model die aktuelle Uhrzeit ab und stellt diese dar (Pull-Pattern FTW!). Ein Push in dieser Situation würde nämlich grundlegendes Wissen über das View im Model voraussetzen. Nämlich welche Daten für das View relevant sind. Damit wäre aber die lose Kopplung zwischen View und Model im Eimer. Das Pull-Pattern mit dem notify() Richtung View ist bereits eine Grauzone: Das View übergibt sich selbst als Referenz an das Model und das Model weiß zwar etwas über das View aber halt nur, dass es das View hin und wieder mal anrufen muss. Die Aufbereitung geschieht also weiterhin komplett im View während der Controller die Steuerung des View und zukünftig auch des Model übernimmt (deshalb schonmal die Bindung zwischen Controller und Model in controller.py, obwohl diese im nachfolgenden Code noch nicht benötigt wird).
Momentan stehe ich ein bisschen wie der Ochs vorm Berg: Das grundlegende MVC ist fertig, aktualisiert aber nur dann, wenn ich eine Taste betätige. Das Observer-Pattern habe ich mal fachgerecht zurückgebaut, da dieses die Übersichtlichkeit und das Verständnis erschwert (nur noch "self._sleep = 0.1" im model.py erinnert an die gescheiterten Versuche). Mit dem Oserver-Pattern ohne Async lief ich nämlich in eines von zwei Problemen:
1. Entweder das Interface musste auf den Statuswechsel des Models warten (Model in Endlosschleife). Dabei war dann keine Interaktion mehr möglich.
2. Oder das Model gab nur dann Informationen weiter, wenn ich eine Taste drücke. Also genau dieselbe Funktionalität wie im nachfolgenden Code.
Über Anregungen und Hinweise, wie das zu einem Async-Programm umgebaut werden könnte, würde ich mich sehr freuen. Ansonsten viel Spaß beim Experimentieren mit dem nachfolgend dargestellten MVC-Pattern.
Viele Grüße
sohqu
[Codebox=python file=model.py]
# -*- coding:utf-8 -*-
import time
class Model():
def __init__(self):
self._sleep = 0.1
# API fuer View
def get_time(self):
return(time.ctime())
[/Codebox]
[Codebox=python file=view.py]
# -*- coding:utf-8 -*-
import curses
class View():
def __init__(self, model_object):
self.mref = model_object
self.actual_time = None
# API fuer Controller
def update_view(self):
self.actual_time = self.mref.get_time()
def draw_view(self, scr):
scr.clear()
scr.border()
scr.addstr(1, 1, "UHR", curses.A_UNDERLINE)
scr.addstr(2, 1, "Aktuelle Uhrzeit: " + str(self.actual_time))
scr.addstr(4, 1, "Taste 'q' zum Beenden.")
[/Codebox]
[Codebox=python file=controller.py]
# -*- coding:utf-8 -*-
class Controller():
def __init__(self, model_object, view_object):
self.mref = model_object
self.vref = view_object
def run_interface(self, scr):
self.vref.update_view()
self.vref.draw_view(scr)
while True:
event = scr.getch()
if event == ord('q'):
break
else:
self.vref.update_view()
self.vref.draw_view(scr)
[/Codebox]
[Codebox=python file=main.py]
# -*- coding:utf-8 -*-
from model import Model
from view import View
from controller import Controller
import curses
import sys
def start():
m = Model()
v = View(m)
c = Controller(m, v)
curses.wrapper(c.run_interface)
if __name__ == "__main__":
sys.exit(start())
[/Codebox]
nachdem ich einigermaßen das Konzept Model-View-Controller (nach Trygve Reenskaug von 1979) verdaut habe, würde ich gerne zum besseren Verständnis ein kleines Programm schreiben.
Dieses soll mit Python 2.7.10 möglichst ohne Zusatzmodule auskommen. Also Module wie Asyncio in Python 3 stehen mir zu Lernzwecken ausdrücklich nicht zur Verfügung.
Ziel des Programms ist eine von View und Controller entkoppelte Abfrage der aktuellen Uhrzeit des Systems, welche natürlich im Model erfasst und bereitgehalten wird. Nach einem Statuswechsel des Model fragt das View die Daten automatisiert ab.
Die Ausgabe des View geht der Übersichtlichkeit halber zur Kommandozeile. Der Controller soll zunächst einmal nur das Programm beenden (Steuerung des View). Später sind auch Steuerungsmöglichkeiten des Model angedacht (z.B. Speicherung zweier Zeitpunkte und Darstellungsänderungen des View, dass genau diese Zeitpunkte dargestellt werden sollen).
Das Lösungskonzept dieser Aufgabe ist denkbar einfach:
Führe im Hintergrund eine Endlosschleife durch, die die Uhrzeit alle 0,1 Sekunden abfragt. Sobald sich die Uhrzeit ändert, soll dem View signalisiert werden, dass sich die Uhrzeit geändert hat und das View aktualisieren kann. Daraufhin macht das View das einzig sinnvolle in dieser Konstellation: Es fragt vom Model die aktuelle Uhrzeit ab und stellt diese dar (Pull-Pattern FTW!). Ein Push in dieser Situation würde nämlich grundlegendes Wissen über das View im Model voraussetzen. Nämlich welche Daten für das View relevant sind. Damit wäre aber die lose Kopplung zwischen View und Model im Eimer. Das Pull-Pattern mit dem notify() Richtung View ist bereits eine Grauzone: Das View übergibt sich selbst als Referenz an das Model und das Model weiß zwar etwas über das View aber halt nur, dass es das View hin und wieder mal anrufen muss. Die Aufbereitung geschieht also weiterhin komplett im View während der Controller die Steuerung des View und zukünftig auch des Model übernimmt (deshalb schonmal die Bindung zwischen Controller und Model in controller.py, obwohl diese im nachfolgenden Code noch nicht benötigt wird).
Momentan stehe ich ein bisschen wie der Ochs vorm Berg: Das grundlegende MVC ist fertig, aktualisiert aber nur dann, wenn ich eine Taste betätige. Das Observer-Pattern habe ich mal fachgerecht zurückgebaut, da dieses die Übersichtlichkeit und das Verständnis erschwert (nur noch "self._sleep = 0.1" im model.py erinnert an die gescheiterten Versuche). Mit dem Oserver-Pattern ohne Async lief ich nämlich in eines von zwei Problemen:
1. Entweder das Interface musste auf den Statuswechsel des Models warten (Model in Endlosschleife). Dabei war dann keine Interaktion mehr möglich.
2. Oder das Model gab nur dann Informationen weiter, wenn ich eine Taste drücke. Also genau dieselbe Funktionalität wie im nachfolgenden Code.
Über Anregungen und Hinweise, wie das zu einem Async-Programm umgebaut werden könnte, würde ich mich sehr freuen. Ansonsten viel Spaß beim Experimentieren mit dem nachfolgend dargestellten MVC-Pattern.
Viele Grüße
sohqu
[Codebox=python file=model.py]
# -*- coding:utf-8 -*-
import time
class Model():
def __init__(self):
self._sleep = 0.1
# API fuer View
def get_time(self):
return(time.ctime())
[/Codebox]
[Codebox=python file=view.py]
# -*- coding:utf-8 -*-
import curses
class View():
def __init__(self, model_object):
self.mref = model_object
self.actual_time = None
# API fuer Controller
def update_view(self):
self.actual_time = self.mref.get_time()
def draw_view(self, scr):
scr.clear()
scr.border()
scr.addstr(1, 1, "UHR", curses.A_UNDERLINE)
scr.addstr(2, 1, "Aktuelle Uhrzeit: " + str(self.actual_time))
scr.addstr(4, 1, "Taste 'q' zum Beenden.")
[/Codebox]
[Codebox=python file=controller.py]
# -*- coding:utf-8 -*-
class Controller():
def __init__(self, model_object, view_object):
self.mref = model_object
self.vref = view_object
def run_interface(self, scr):
self.vref.update_view()
self.vref.draw_view(scr)
while True:
event = scr.getch()
if event == ord('q'):
break
else:
self.vref.update_view()
self.vref.draw_view(scr)
[/Codebox]
[Codebox=python file=main.py]
# -*- coding:utf-8 -*-
from model import Model
from view import View
from controller import Controller
import curses
import sys
def start():
m = Model()
v = View(m)
c = Controller(m, v)
curses.wrapper(c.run_interface)
if __name__ == "__main__":
sys.exit(start())
[/Codebox]