Форум сайта python.su
0
Конкретного вопроса нет, тема для рассуждения, критики.
Идея 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)
Офлайн
0
Решил сам себя покритиковать, сделать некоторый рефакторинг. Заменю в некоторых местах data на *args, чтобы не проверять data на пустоту и более конкретно задам выходные функции в модели. Чтобы не разводить версии - поправлю прямо в первом сообщении.
—
Ещё. После рефакторинга понял, что карты каждый должен делать себе сам. Тогда не нужен базовый контроллер, не нужны его реализации, заточенные под конкретный Вид. Поправляю всё.
Отредактировано (Фев. 5, 2010 11:23:03)
Офлайн
72
Странное решение. Зачем-то инвертированы зависимости: модель знает о контролере, виды не знают о модели. Обычно наоборот - модель понятия не имеет о других участниках, а виды умеют прочитать данные из модели.
Офлайн
0
PooHВсё. Поправил. Модель не знает о контроллере. Но то, что Вид может читать из Модели, управлять ею - мне не нравится. Надо подумать.
Странное решение. Зачем-то инвертированы зависимости: модель знает о контролере, виды не знают о модели. Обычно наоборот - модель понятия не имеет о других участниках, а виды умеют прочитать данные из модели.
Отредактировано (Фев. 5, 2010 13:23:09)
Офлайн
0
Всё мое творчество вылилось в деградировании контроллера до простой функции. Сделал вариант почти без контроллера и без словарей. Смысл в использовании квазиабстрактных классов. Обычно они наследуются, но я выворотил наизнанку - у меня они генерируются из “потомка”. Это позволяет заткнуть несколько указателей в классе-контейнере одной и той же функцией в консольном Виде (завтра я сам не пойму, что тут написал).
Пока не могу сказать, что он лучше того, что в первом сообщении.
Модель. 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()
Офлайн
0
Всё мое творчество вылилось в деградировании контроллера до простой функции.
Типовое решение модель—представление—контроллер — одно из наиболее часто цитируемых (и, к сожалению, неверно истолковываемых).
Отделение представления от модели — один из основополагающих принципов, на котором держится все проектирование программного обеспечения, и пренебрегать им можно только тогда, когда речь идет о совсем простых системах, в которых модель вообще не имеет какого-либо реального поведения. Как только в приложении появляется невизуализированная логика, разделение становится крайне необходимым.
…
Отделение представления от контроллера не так важно, поэтому рекомендую проводить его только в том случае, когда оно действительно нужно. Подобная необходимость практически не возникает в системах с толстыми клиентами, а вот в Web-интерфейсах отделение контроллера весьма полезно.
Офлайн