重构为MVC模式 - 对视图与控制器分离的疑问

6 投票
2 回答
1256 浏览
提问于 2025-04-16 01:26

我正在尝试把我的应用程序(有1000多行的图形界面代码)重构成MVC风格的模式。逻辑代码已经和图形界面分开了,所以这不是问题。我的担心是如何把视图和控制器分开。我理解MVC的基本原则,这个wxpython维基上的教程对我帮助很大,但代码示例有点简单,当我试图把这个原则应用到我自己的项目时,感觉有些疑惑,因为我的项目要复杂得多。

结构的一个片段..

我有一个MainWindow,里面有很多小部件,包括一个noteBook(标签式的部分),这个noteBook里有几个标签,其中一个标签(我称之为FilterTab)里有两个我称之为FilterPanel的类实例。这个FilterPanel是一个面板,里面有一个列表框、三个按钮,一个是清空,一个是移除,还有一个是添加列表中的项目。根据在创建类实例时传入的标志,添加按钮的事件可以创建不同类型的对话框,比如文本输入对话框或目录选择器等等。

这只是图形界面的一部分,结构相当复杂,事件处理程序埋在FilterPanel类里。

如果我想把这部分转换为MVC模式,我就得在控制器里绑定每个FilterPanel实例的按钮事件(而不是在FilterPanel类里绑定)——在这种情况下,有两个FilterPanel实例。

所以我每个按钮可能会有这样的代码(每个FilterPanel有3个按钮 * 面板实例的数量),再加上事件处理程序..

 self.mainWindow.filterTab.dirFilterPnl.Bind(wx.EVT_BUTTON,
                                    self.onAdd_dirFilterPnl, 
                            self.mainWindow.filterTab.dirFilterPnl.addBtn,
                            self.mainWindow.filterTab.dirFilterPnl.addBtn.GetId()
                                    )

这会增加很多额外的代码(如果我只有两个FilterPanel实例,事件处理程序的数量会翻倍)

所以我想知道我这样做是不是正确的方向?

2 个回答

1

wxPython自带了一个叫pubsub的功能,它遵循发布/订阅的方法。这种方法和pydispatcher有点像,不过它们的实现方式不太一样。wxPython的维基上有一些关于如何在你的程序中使用pubsub的例子,还有一个简单的教程可以参考:

http://www.blog.pythonlibrary.org/2010/06/27/wxpython-and-pubsub-a-simple-tutorial/

在图形用户界面(GUI)中,MVC的概念和Django或TurboGears中的不太一样。我发现我可以把大部分逻辑放在控制器里,而在我的“视图”中,只需要绑定到控制器就可以了。比如这样:

view.py

btn.Bind(wx.EVT_BUTTON, self.onButton)

def onButton(self, event): controller.someMethod(*args, **kwargs)

根据计算的内容,我可能会在控制器中运行一个线程,然后使用wx.CallAfter和pubsub在稍后发布结果。

9

如果我想把那部分改成MVC模式,我就得在控制器里为每个FilterPanel的实例绑定按钮事件,而不是在filterPanel类里绑定。

其实不一定!MVC的理念和做法并不意味着“视图”就是简单的小部件;你的FilterPanel可以被看作是一个“复杂的”部件,它可以生成自己的高级“事件”(发给控制器),并进行相应的更新。所以,这个复杂的部件可以处理一些低级的“事件”,并从中合成更高级的事件,然后发送给控制器;控制器不需要知道每个按钮的具体情况,只需要关注它收到的高级事件,比如“用户想选择一个目录用于X目的”或者“用户想输入文本用于Y目的”,然后根据这些事件告诉视图该怎么做。

关键是,视图不会根据它处理的事件做出任何“语义”上的决策,也不会向模型发送任何命令——控制器是所有这些交互中不可或缺的“中介”。

举个例子,想象一下图形界面的最底层有一些非常基础的事件,比如“左键按下”和“左键抬起”——一个“按钮部件”会直接对这些事件做出反应,改变按钮的外观(这是一个“视觉”决策,而不是“战略”决策),最终在合适的时候合成一个更高级的事件,比如“按钮被点击”(当鼠标按下后又抬起,中间没有其他鼠标移动干扰“点击”的假设时)。这个事件会被发送到需要对按钮点击做出反应的更高层。

同样地,你的复杂部件也可以处理这些事件,并为控制器合成更高级的事件。(同样的抽象事件可以由按钮点击、菜单选择、某些按键输入等产生……控制器并不关心这些低层的“视觉”考虑,这属于视图或部件的工作,而视图或部件也不会对这些用户交互硬编码“战略”决策和行动,这都是控制器的工作)。

这种关注点的分离有助于解决测试和应用灵活性等问题;为了获得这些优势,可能需要写更多的代码,而不是把所有东西都硬编码在一起……但如果你选择MVC,就意味着你觉得这个代价是值得的。

wx可能不是实现这个的理想框架,但你可以子类化wx.Event——或者你可以使用像pydispatcher这样的独立事件系统来处理在不同子系统之间流动的更高级事件,从而将控制器与具体的GUI框架选择解耦。我通常使用Qt,我认为它的信号/槽模型比典型的GUI框架的事件系统更好扩展和适应。但这又是一个不同的选择和问题。

撰写回答