## page was renamed from zhArticleTemplate #language:zh #pragma section-numbers on <> 原文 [[http://wiki.wxpython.org/index.cgi/ModelViewController|wxPython wiki :: ModelViewController ]] 翻译:ZhangYunfeng = 模型-视图-控制器 = ModelViewController 简称为 MVC = 简介 = 本页介绍 MVC (ModelViewController) 模式并演示其在Python中的实现方法 在 http://c2.com/cgi/wiki?ModelViewController 可以了解更多关于MVC的信息, 你也可以察看MVC的一种演化模式 ModelViewPresenter = 原理 = (原文出处 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)。 = 示例代码 = (originally posted by Brian Kelly at http://www.techietwo.com/detail-6332577.html) {{{#!python import wx # an observable calls callback functions when the data has # changed #o = Observable() #def func(data): # print "hello", data #o.addCallback(func) #o.set(1) # --| "hello", 1 class Observable: def __init__(self, initialValue=None): self.data = initialValue self.callbacks = {} def addCallback(self, func): self.callbacks[func] = 1 def delCallback(self, func): del self.callback[func] def _docallbacks(self): for func in self.callbacks: func(self.data) def set(self, data): self.data = data self._docallbacks() def get(self): return self.data def unset(self): self.data = None class Model: def __init__(self): self.myMoney = Observable(0) def addMoney(self, value): self.myMoney.set(self.myMoney.get() + value) def removeMoney(self, value): self.myMoney.set(self.myMoney.get() - value) class View(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, -1, "Main View") sizer = wx.BoxSizer(wx.VERTICAL) text = wx.StaticText(self, -1, "My Money") ctrl = wx.TextCtrl(self, -1, "") sizer.Add(text, 0, wx.EXPAND|wx.ALL) sizer.Add(ctrl, 0, wx.EXPAND|wx.ALL) self.moneyCtrl = ctrl ctrl.SetEditable(False) self.SetSizer(sizer) self.moneyCtrl = ctrl def SetMoney(self, money): self.moneyCtrl.SetValue(str(money)) class ChangerWidget(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, -1, "Main View") sizer = wx.BoxSizer(wx.VERTICAL) self.add = wx.Button(self, -1, "Add Money") self.remove = wx.Button(self, -1, "Remove Money") sizer.Add(self.add, 0, wx.EXPAND|wx.ALL) sizer.Add(self.remove, 0, wx.EXPAND|wx.ALL) self.SetSizer(sizer) class Controller: def __init__(self, app): self.model = Model() self.view1 = View(None) self.view2 = ChangerWidget(self.view1) self.MoneyChanged(self.model.myMoney.get()) self.view2.add.Bind(wx.EVT_BUTTON, self.AddMoney) self.view2.remove.Bind(wx.EVT_BUTTON, self.RemoveMoney) self.model.myMoney.addCallback(self.MoneyChanged) self.view1.Show() self.view2.Show() def AddMoney(self, evt): self.model.addMoney(10) def RemoveMoney(self, evt): self.model.removeMoney(10) def MoneyChanged(self, money): self.view1.SetMoney(money) app = wx.PySimpleApp() Controller(app) app.MainLoop() }}} = 讨论 = <>