Идея MVC теоретически проста, но на практике трудно понять, почувствовать, будет ли паттерн работать, если поменять Вид. Потому решил сделать простейшую программу с двумя версиями: для консоли и для GUI, в которой бы Модель оставалась неизменяемой, а Контроллер бы изменялся незначительно. И чтобы Модель и Вид не знали друг о друге ничего.
ТЗ:
1. Программа выдаёт каждые две секунды сообщение “Timer”.
2. Режим такой выдачи должен быть отключаем/включаем по команде пользователя.
3. По команде от пользователя должно выдаваться сообщение “Hello”.
4. В GUI-варианте сообщения “Timer” и “Hello” выводятся в разных виджетах, команды посылаются с помощью меню.
GUI-вариант реализую с помощью wxPython. В принципе, вернее было бы реализовать с помощью Tk, но пока ленюсь. Возможно, потом сделаю, если останется смысл.
Анализ:
Основная идея следующая: Модель и Вид посылают с помощью Контроллера данные и команды неизвестному адресату с определенным идентификатором. Контроллер содержит карты соответствия идентификаторов и адресатов.
Реализация:
Модель. model.py
Таймер реализован с помощью потоков (возможно, это не очень красиво).
import threading
class Model:
def __init__(self, outers):
self.timer_outer = outers["timerOuter"] # see the controller.mapView keys
self.hello_outer = outers["helloOuter"]
self.start()
def make_map(self):
modelMap = {'t': self.timer_switching, # timer
'g': self.greeting, # greeting
'q': None} # quit
return modelMap
def start(self):
self.timer = threading.Timer(2, function=self._timer_proc)
self.timer.start()
self.timer_on = True
def timer_switching(self):
self.timer_on = not self.timer_on
def greeting(self):
self.hello_outer("Hello")
def stop(self):
self.timer.cancel()
def _timer_proc(self):
if self.timer_on:
self.timer_outer("Timer")
self.timer.run()
from model import Model
class Controller:
def __init__(self, viewMap):
self.model = Model(viewMap)
self.modelMap = self.model.make_map()
def command(self, id, *args):
if id in self.modelMap:
func = self.modelMap[id]
if not func:
self.model.stop() # concrete
return False
func(*args)
return True
from controller import Controller
class View:
def __init__(self):
self.make_controller()
self.run_func()
def make_controller(self):
viewMap = {"timerOuter": self.out_func, "helloOuter": self.out_func}
self.controller = Controller(viewMap)
def out_func(self, data):
print data
def run_func(self):
print "commands: 't'(timer), 'g'(greeting) and 'q'(quit)"
while(True):
id = raw_input()
if not self.controller.command(id):
break
if __name__ == '__main__':
view = View()
from controller import Controller
import wx
class View(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'MVC Example', size=(300, 250))
menuBar = wx.MenuBar()
menu = wx.Menu()
items = (("&Greeting", self.on_greeting), ("&Timer", self.on_timer),("&Quit", self.on_close))
for item in items:
menuItem = menu.Append(-1, item[0])
self.Bind(wx.EVT_MENU, item[1], menuItem)
menuBar.Append(menu, "&File")
self.SetMenuBar(menuBar)
sizer = wx.BoxSizer(wx.HORIZONTAL)
def set_edit():
edit = wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE)
sizer.Add(edit, 1, wx.EXPAND)
return edit
self.textGreeting, self.textTimer = set_edit(), set_edit()
self.SetSizer(sizer)
self.Bind(wx.EVT_CLOSE, self.on_close)
self.make_controller()
def make_controller(self):
viewMap = {"timerOuter": self.out_timer_func,
"helloOuter": self.out_greeting_func}
self.controller = Controller(viewMap)
def out_timer_func(self, data):
self.textTimer.SetValue(self.textTimer.GetValue() + data + "\r\n")
def out_greeting_func(self, data):
self.textGreeting.SetValue(self.textGreeting.GetValue() + data + "\r\n")
def on_greeting(self, evt):
self.controller.command('g')
def on_timer(self, evt):
self.controller.command('t')
def on_close(self, evt):
self.controller.command('q')
self.Destroy()
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = View()
frame.Show()
app.MainLoop()
—
В редакции немного поменял Модель и базовый Контроллер.
—
Вторая редакция: выкинул базовый Контроллер. Выкинул наследников базового Контроллера, заточенных под конкретный Вид.