Size: 8721
Comment: typo
|
← Revision 7 as of 2009-12-25 07:08:32 ⇥
Size: 8723
Comment: converted to 1.6 markup
|
Deletions are marked like this. | Additions are marked like this. |
Line 5: | Line 5: |
[[TableOfContents]] | <<TableOfContents>> |
Line 7: | Line 7: |
原文 [http://wiki.wxpython.org/index.cgi/ModelViewController wxPython wiki :: ModelViewController ] | 原文 [[http://wiki.wxpython.org/index.cgi/ModelViewController|wxPython wiki :: ModelViewController ]] |
Line 170: | Line 170: |
[[Include(/Discuss)]] | <<Include(/Discuss)>> |
原文 wxPython wiki :: ModelViewController
翻译:ZhangYunfeng
1. 模型-视图-控制器
ModelViewController 简称为 MVC
2. 简介
本页介绍 MVC (ModelViewController) 模式并演示其在Python中的实现方法
在 http://c2.com/cgi/wiki?ModelViewController 可以了解更多关于MVC的信息, 你也可以察看MVC的一种演化模式 ModelViewPresenter
3. 原理
(原文出处 http://mail.python.org/pipermail/python-list/2006-January/319314.html by has)
MVC模式关注于内容的分离。
Model(模型)负责管理程序中的数据(私有数据和客户数据)。View/Controller(视图/控制器)负责为外界提供与程序中的客户数据进行交互的手段。
模型提供了内部接口(API),允许程序的其他部分与之交互。视图/控制器提供外部接口(GUI/CLI/web form/high-level IPC/等等),允许程序外部的世界与程序进行交互。
模型负责保持数据的完整性,因为数据一旦被破坏,所有的东西就 game over 了。视图/控制器负责保持UI的完整性, 确保所有的文字视图显示的都是最新的数据,使不适用于当前焦点的菜单失效,等等。
模型不包含视图/控制器代码;没有GUI窗口类,没有对话框布置代码,不接受用户输入。视图/控制器不包含模型代码;没有URLs验证和SQL查询代码,也不包含数据原始状态: 窗口中的任何数据仅仅是为显示目的而存在,而且也仅仅是模型中数据的真实反映。
如何验证真正的MVC设计呢?如果程序不存在视图/控制器部分,它也应该具有完整的功能。当然,这种情况下外部世界与它进行交互会存在困难,但是 只要知道适当的模型API,程序应该能够正常的保持和操作数据。
为什么能够达到这种效果?最简单的答案:都是因为模型与视图/控制器层之间的弱耦合关系。但是这并不是它的全部内容。MVC模式的关键是 这些连接的走向:所有的指令都是从视图/控制器指向模型的。模型从不告诉视图/控制器去做什么。
为什么呢?因为在MVC中,允许视图/控制器对模型有一些了解(特别是模型的API),但是不允许模型了解视图/控制器的任何情况。
为什么呢?因为MVC就是要建立一个内容上完整分离的结构。
为什么呢?是为了防止无法控制程序的复杂性,导致你这位程序开发者精疲力尽。程序越大,其部件数量越多。这些部件之间的连接越多,开发人员就越难以控制/扩展/替换各个部件,甚至于无法搞清整个系统如何工作。问问你自己:当你看到程序结构图时,你愿意看到一颗树还是猫爪印?MVC模式拒绝回环连接(B可以连接A,但A不能连接B。在这里,A是模型,B是视图/控制器),从而避免了后者的产生。
另外,如果你够聪明,你会意识到刚才描述的单向约束中有一个问题:在模型根本不知道视图/控制器的情况下,如何通知视图/控制器其用户数据的变化呢?不要担心,这里有一个解决方案,虽然刚开始看起来有一些绕圈子,但实际上它相当的简洁,我们待会儿会讲到它。
在实际项目中,视图/控制器对象会通过模型的API,1. 让模型做一些事情(执行命令),2. 让模型给它一些东西(返回数据)。视图/控制器层向模型层发出指令并从模型层中获取信息。
例如一个 MyCoolListControl 类,应该在需要时能够从下层获取所需的数据。对于list widget的情况,一般意味着询问有多少个值,然后返回每一个值,因为这是完成这种操作最简单最宽松的方式,同时将耦合保持到最低限度。而如果 widget 想向用户展示按照字母排序的结果,那就是它自己的工作,当然要它自己负责完成。
现在,最后一个迷题(也就是我们之前提到的): 在基于MVC的系统中如何保持UI的显示同步于模型的状态呢?
这就是问题所在:很多的视图对象都是状态性的,比如 checkbox 可能处于选择或者未选择状态,文字框可能包含一些可以编辑的文字。但是,MVC规定所有的用户数据保存在模型层中,所以其他层中的为了显示目的而存在的任何数据(checkbox的状态,文字框中的当前文字)必须是模型数据的辅助副本。但如果模型状态改变了,视图中的副本就不再准确而应该被刷新。
但是如何操作呢?MVC模式禁止模型向视图层发出相关信息的最新副本,甚至不允许模型向视图发出消息,表明其状态的变化。
是的,几乎就是这样。Ok,不允许模型层与其他层直接对话,因为这就要求它知道其他层的信息,而且MVC的规定也不允许这样做。但是,如果森林中的一颗树倒下了,而没有人听到,那么它发出声音了吗?
答案就是建立一套通告系统,为模型层提供一个空间,让它可以发出通告,说它已经完成了一些有趣的事情。其他层可以向通告系统发出监听器,监听它们所感兴趣的的通告。模型层根本不需要知道谁在监听(甚至是否有人在监听);它仅仅发出通告,然后就把它抛在了脑后。如果有人听到了这个通告,并且想随之做一些事情 - 比如向模型询问一些新的数据以便更新显示 - 这样就搞定了。模型只是将它所发出的通告列表作为它的API定义的一部分;其他人想拿这个知识做什么事的话,随便。
MVC is preserved,and everyone is happy. 你的应用程序框架可能已经提供了内置的通告系统,你也可以自己写一个(参考 ObserverPattern)。
4. 示例代码
(originally posted by Brian Kelly at http://www.techietwo.com/detail-6332577.html)
1 import wx
2
3 # an observable calls callback functions when the data has
4 # changed
5 #o = Observable()
6 #def func(data):
7 # print "hello", data
8 #o.addCallback(func)
9 #o.set(1)
10 # --| "hello", 1
11
12 class Observable:
13 def __init__(self, initialValue=None):
14 self.data = initialValue
15 self.callbacks = {}
16
17 def addCallback(self, func):
18 self.callbacks[func] = 1
19
20 def delCallback(self, func):
21 del self.callback[func]
22
23 def _docallbacks(self):
24 for func in self.callbacks:
25 func(self.data)
26
27 def set(self, data):
28 self.data = data
29 self._docallbacks()
30
31 def get(self):
32 return self.data
33
34 def unset(self):
35 self.data = None
36
37 class Model:
38 def __init__(self):
39 self.myMoney = Observable(0)
40
41 def addMoney(self, value):
42 self.myMoney.set(self.myMoney.get() + value)
43
44 def removeMoney(self, value):
45 self.myMoney.set(self.myMoney.get() - value)
46
47
48 class View(wx.Frame):
49 def __init__(self, parent):
50 wx.Frame.__init__(self, parent, -1, "Main View")
51 sizer = wx.BoxSizer(wx.VERTICAL)
52 text = wx.StaticText(self, -1, "My Money")
53 ctrl = wx.TextCtrl(self, -1, "")
54 sizer.Add(text, 0, wx.EXPAND|wx.ALL)
55 sizer.Add(ctrl, 0, wx.EXPAND|wx.ALL)
56 self.moneyCtrl = ctrl
57 ctrl.SetEditable(False)
58 self.SetSizer(sizer)
59 self.moneyCtrl = ctrl
60
61 def SetMoney(self, money):
62 self.moneyCtrl.SetValue(str(money))
63
64 class ChangerWidget(wx.Frame):
65 def __init__(self, parent):
66 wx.Frame.__init__(self, parent, -1, "Main View")
67 sizer = wx.BoxSizer(wx.VERTICAL)
68 self.add = wx.Button(self, -1, "Add Money")
69 self.remove = wx.Button(self, -1, "Remove Money")
70 sizer.Add(self.add, 0, wx.EXPAND|wx.ALL)
71 sizer.Add(self.remove, 0, wx.EXPAND|wx.ALL)
72 self.SetSizer(sizer)
73
74 class Controller:
75 def __init__(self, app):
76 self.model = Model()
77 self.view1 = View(None)
78 self.view2 = ChangerWidget(self.view1)
79 self.MoneyChanged(self.model.myMoney.get())
80 self.view2.add.Bind(wx.EVT_BUTTON, self.AddMoney)
81 self.view2.remove.Bind(wx.EVT_BUTTON, self.RemoveMoney)
82
83 self.model.myMoney.addCallback(self.MoneyChanged)
84 self.view1.Show()
85 self.view2.Show()
86
87 def AddMoney(self, evt):
88 self.model.addMoney(10)
89
90 def RemoveMoney(self, evt):
91 self.model.removeMoney(10)
92
93 def MoneyChanged(self, money):
94 self.view1.SetMoney(money)
95
96
97 app = wx.PySimpleApp()
98 Controller(app)
99 app.MainLoop()
5. 讨论