Форум сайта python.su
Конкретного вопроса нет, тема для рассуждения, критики.
Идея 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()
Отредактировано (Фев. 5, 2010 11:33:06)
Офлайн
Решил сам себя покритиковать, сделать некоторый рефакторинг. Заменю в некоторых местах data на *args, чтобы не проверять data на пустоту и более конкретно задам выходные функции в модели. Чтобы не разводить версии - поправлю прямо в первом сообщении.
—
Ещё. После рефакторинга понял, что карты каждый должен делать себе сам. Тогда не нужен базовый контроллер, не нужны его реализации, заточенные под конкретный Вид. Поправляю всё.
Отредактировано (Фев. 5, 2010 11:23:03)
Офлайн
Странное решение. Зачем-то инвертированы зависимости: модель знает о контролере, виды не знают о модели. Обычно наоборот - модель понятия не имеет о других участниках, а виды умеют прочитать данные из модели.
Офлайн
PooHВсё. Поправил. Модель не знает о контроллере. Но то, что Вид может читать из Модели, управлять ею - мне не нравится. Надо подумать.
Странное решение. Зачем-то инвертированы зависимости: модель знает о контролере, виды не знают о модели. Обычно наоборот - модель понятия не имеет о других участниках, а виды умеют прочитать данные из модели.
Отредактировано (Фев. 5, 2010 13:23:09)
Офлайн
Всё мое творчество вылилось в деградировании контроллера до простой функции. Сделал вариант почти без контроллера и без словарей. Смысл в использовании квазиабстрактных классов. Обычно они наследуются, но я выворотил наизнанку - у меня они генерируются из “потомка”. Это позволяет заткнуть несколько указателей в классе-контейнере одной и той же функцией в консольном Виде (завтра я сам не пойму, что тут написал).
Пока не могу сказать, что он лучше того, что в первом сообщении.
Модель. model.py
import threading
class Model:
def __init__(self, viewMap):
self.viewMap = viewMap # see the controller.mapView keys
self.start()
def make_map(self):
class map: pass
map.timer_in = self.timer_switching
map.greeting_in = self.greeting
map.quit = self.stop
return map
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.viewMap.hello_out("Hello")
def stop(self):
self.timer.cancel()
def _timer_proc(self):
if self.timer_on:
self.viewMap.timer_out("Timer")
self.timer.run()
def get_model_map(viewMap):
model = Model(viewMap)
return model.make_map()
from model import get_model_map
class View:
def __init__(self):
self.make_maps()
self.run_func()
def make_maps(self):
class map: pass
map.timer_out = self.out_func
map.hello_out = self.out_func
self.modelMap = get_model_map(map)
def out_func(self, data):
print data
def run_func(self):
commands = {'t': self.modelMap.timer_in,
'g': self.modelMap.greeting_in,
'q': self.modelMap.quit}
print "commands: 't'(timer), 'g'(greeting) and 'q'(quit)"
while(True):
id = raw_input()
if id in commands:
commands[id]()
if id == 'q':
break
if __name__ == '__main__':
view = View()
from model import get_model_map
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_maps()
def make_maps(self):
class map: pass
map.timer_out = self.out_timer_func
map.hello_out = self.out_greeting_func
self.modelMap = get_model_map(map)
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.modelMap.greeting_in()
def on_timer(self, evt):
self.modelMap.timer_in()
def on_close(self, evt):
self.modelMap.quit()
self.Destroy()
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = View()
frame.Show()
app.MainLoop()
Офлайн
Всё мое творчество вылилось в деградировании контроллера до простой функции.
Типовое решение модель—представление—контроллер — одно из наиболее часто цитируемых (и, к сожалению, неверно истолковываемых).
Отделение представления от модели — один из основополагающих принципов, на котором держится все проектирование программного обеспечения, и пренебрегать им можно только тогда, когда речь идет о совсем простых системах, в которых модель вообще не имеет какого-либо реального поведения. Как только в приложении появляется невизуализированная логика, разделение становится крайне необходимым.
…
Отделение представления от контроллера не так важно, поэтому рекомендую проводить его только в том случае, когда оно действительно нужно. Подобная необходимость практически не возникает в системах с толстыми клиентами, а вот в Web-интерфейсах отделение контроллера весьма полезно.
Офлайн