Уведомления

Группа в Telegram: @pythonsu

#1 Фев. 4, 2010 14:52:50

Kogrom
От:
Зарегистрирован: 2009-12-03
Сообщения: 160
Репутация: +  0  -
Профиль   Отправить e-mail  

MVC

Конкретного вопроса нет, тема для рассуждения, критики.

Идея 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()
Базовый контроллер (без карт идентификаторов). controller.py

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()
GUI вид:

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)

Офлайн

#2 Фев. 5, 2010 09:50:21

Kogrom
От:
Зарегистрирован: 2009-12-03
Сообщения: 160
Репутация: +  0  -
Профиль   Отправить e-mail  

MVC

Решил сам себя покритиковать, сделать некоторый рефакторинг. Заменю в некоторых местах data на *args, чтобы не проверять data на пустоту и более конкретно задам выходные функции в модели. Чтобы не разводить версии - поправлю прямо в первом сообщении.



Ещё. После рефакторинга понял, что карты каждый должен делать себе сам. Тогда не нужен базовый контроллер, не нужны его реализации, заточенные под конкретный Вид. Поправляю всё.



Отредактировано (Фев. 5, 2010 11:23:03)

Офлайн

#3 Фев. 5, 2010 11:26:09

PooH
От:
Зарегистрирован: 2006-12-05
Сообщения: 1948
Репутация: +  72  -
Профиль   Отправить e-mail  

MVC

Странное решение. Зачем-то инвертированы зависимости: модель знает о контролере, виды не знают о модели. Обычно наоборот - модель понятия не имеет о других участниках, а виды умеют прочитать данные из модели.



Вот здесь один из первых отарков съел лаборанта. Это был такой умный отарк, что понимал даже теорию относительности. Он разговаривал с лаборантом, а потом бросился на него и загрыз…

Офлайн

#4 Фев. 5, 2010 11:36:30

Kogrom
От:
Зарегистрирован: 2009-12-03
Сообщения: 160
Репутация: +  0  -
Профиль   Отправить e-mail  

MVC

PooH
Странное решение. Зачем-то инвертированы зависимости: модель знает о контролере, виды не знают о модели. Обычно наоборот - модель понятия не имеет о других участниках, а виды умеют прочитать данные из модели.
Всё. Поправил. Модель не знает о контроллере. Но то, что Вид может читать из Модели, управлять ею - мне не нравится. Надо подумать.

Добавлено позже.
Подумал. Я делаю так: Вид не знает ничего о реализации модели, а только то, что у неё есть какие-то абстрактные функции. То же и с Моделью по отношению к Виду. А Контроллер как раз является прослойкой, которая дает указатели на эти абстрактные функции.

Так можно менять и Вид и Модель, главное, чтобы у них был набор неких абстрактных функций. Контроллер остаётся почти без изменений.

Конечно, Контроллер мог бы давать Виду более конкретные указатели на функции Модели. Об этом подумаю.

Добавлено ещё позже.

Ещё подумал. В общем, контроллер мог бы возвращать словарь функций Модели Виду, и Вид бы хранил и использовал определенные указатели на эти функции. Это немного бы повысило быстродействие, но усложнило бы код, внесло бы ненадежные места. Однако, быстродействие взаимодействие с пользователем - вещь неинтересная, когда речь идёт о выигрыше пары миллисекунд.

С другой стороны, Модель бы тоже могла обращаться к Виду через карту (словарь), не храня указателей. Но я оставил такой принцип, чтобы помнить о нём.

Ну и главный принцип вспомнил: обычно Модель посылает Виду сообщение обновиться (через Контроллер), а потом Вид берёт напрямую данные из Модели. Но нут не только лишние действия, но и лишние зависимости.



Отредактировано (Фев. 5, 2010 13:23:09)

Офлайн

#5 Фев. 5, 2010 22:37:21

Kogrom
От:
Зарегистрирован: 2009-12-03
Сообщения: 160
Репутация: +  0  -
Профиль   Отправить e-mail  

MVC

Всё мое творчество вылилось в деградировании контроллера до простой функции. Сделал вариант почти без контроллера и без словарей. Смысл в использовании квазиабстрактных классов. Обычно они наследуются, но я выворотил наизнанку - у меня они генерируются из “потомка”. Это позволяет заткнуть несколько указателей в классе-контейнере одной и той же функцией в консольном Виде (завтра я сам не пойму, что тут написал).

Пока не могу сказать, что он лучше того, что в первом сообщении.

Модель. 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()
GUI Вид.
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()



Офлайн

#6 Авг. 4, 2016 06:18:09

streetmover
От:
Зарегистрирован: 2010-06-25
Сообщения: 25
Репутация: +  0  -
Профиль   Отправить e-mail  

MVC

Всё мое творчество вылилось в деградировании контроллера до простой функции.

Что вполне закономерно. Цитаты из кн. Мартина Фаулера “Архитектура корпоративных приложений”:
с. 347:
Типовое решение модель—представление—контроллер — одно из наиболее часто цитируемых (и, к сожалению, неверно истолковываемых).

с. 350:
Отделение представления от модели — один из основополагающих принципов, на котором держится все проектирование программного обеспечения, и пренебрегать им можно только тогда, когда речь идет о совсем простых системах, в которых модель вообще не имеет какого-либо реального поведения. Как только в приложении появляется невизуализированная логика, разделение становится крайне необходимым.

Отделение представления от контроллера не так важно, поэтому рекомендую проводить его только в том случае, когда оно действительно нужно. Подобная необходимость практически не возникает в системах с толстыми клиентами, а вот в Web-интерфейсах отделение контроллера весьма полезно.



Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version