我有一个窗口小部件,显示文件系统层次结构以方便浏览(基本上是树控件和一些相关的工具栏按钮,例如“刷新”)。这些小部件中的每一个都有一组基本目录供它显示(递归)。假设用户可以在他们觉得方便时实例化这些小部件中的许多小部件。请注意,这些小部件与任何业务数据都不对应 - 它们与模型无关。
(每个小部件)基本目录集应该位于良好的MVC设计中吗?
按下刷新按钮时,控制器会捕获一个事件,该事件包含相应的文件系统 - 浏览器小部件。控制器确定该特定小部件的基本目录(以某种方式),遍历该目录路径,并向小部件传递一些数据以进行渲染。
我可以考虑存储基本目录的两个地方:
{widget: base_directory_set}
映射。第二种方式允许以后轻松扩展控制器职责,因为在控制器中放置东西往往会这样做 - 例如,如果我决定后来想要确定所有这些小部件的所有基本目录的集合。
我可能缺少一些MVC知识,可以很好地解决这类问题。
答案 0 :(得分:2)
使得这种设计难以使MVC符合的异常(从MVC观点来看)是你想要显示通过概念化“不存在于模型中”的信息。在MVC中没有“不存在于模型中的信息”这样的东西:它的概念根是“模型保存所有信息,视图只做演示任务,控制器调解用户交互”
您显示的信息很可能“不对应任何商业数据”,但(在MVC世界观中),不意味着信息“独立于模型” “,因为没有这样的东西 - 它只是意味着你需要另一个模型类(除了你用来保存”商业数据“之外),以保存这个”非商业“数据! - )
因此,当用户“实例化窗口小部件”时(创建目录显示视图,可能是某些主/协调视图上的某些用户操作,可能是另一个现有窗口小部件,如果“克隆”是实例化窗口小部件的方法之一),控制器负责创建窗口小部件对象和“目录显示模型类”的实例,并在它们之间建立连接(通常通过在窗口小部件上设置对相关模型实例的引用),以及告诉模型进行初始加载信息。当窗口小部件上的用户操作暗示对模型执行操作时,控制器从事件中涉及的窗口小部件检索对模型实例的引用,并向该实例发送适当的请求(模型的业务是为了让视图[s]对它感兴趣知道信息的变化 - 通常是通过一些观察者模式;它绝对不控制器的业务来为视图提供信息 - 这真的是非常来自MVC的不同方法!)。
在您的情况下,MVC所需的架构投资是否值得采用更粗糙的方法,在这种方法中,信息流不那么原始且应该存在的模型不存在?我是一个实用主义者,我绝对不会在MVC的祭坛上崇拜,但我认为在这种情况下,对声音,清晰建筑的(相对较小的)投资可能确实会大量回报。这是一个设想可能的变化方向的问题 - 例如,你现在不需要什么功能(但很快就会很快进入图片)如果你选择合适的MVC路线,那将是微不足道的。否则就会成为特殊克拉的噩梦(或者需要对整个架构进行一些有点痛苦的重构)?所有可能的东西,从想要在不同的小部件中显示相同的目录信息到拥有更智能的“目录信息观察”模型,可以在需要时自动刷新自己(并通过通常的观察者模式直接向感兴趣的视图提供新信息) ,没有控制器的参与),使用MVC是自然而轻松的(嘿,这毕竟是MVC的整个点,所以这并不奇怪! - ),kludgy和脆弱的一个临时切角架构 - 投资小,潜在回报大,适合它!
你可能会注意到前一段的语气,我也不会在“极端编程”的祭坛上崇拜 - 作为一个实用主义者,我会做一点“设计前面” (特别是从一开始就建立一个干净,灵活,可扩展的架构,即使它不是必不可少的现在) - 正是因为,根据我的经验,有点预见和非常谦虚投资,特别是在建筑方面,在项目的生命周期内多次回报(以可扩展性,灵活性,可扩展性,可维护性,安全性等等各种货币,但并非所有项目都适用于每个项目 - - 例如,在您的情况下,安全性和可扩展性并不是真正的问题......但其他方面可能就是! - )。
只是为了一般性,我要指出,我的这种务实态度确实 过度的“;-) - 熟悉一些基本的架构模式(而MVC肯定是其中之一)通常会减少时间和精力方面的初始投资 - 一旦你认识到这样一个经典架构能很好地为你服务,就像在这种情况下一样,很容易看出如何体现它(例如,拒绝“没有M的MVC”的想法!),并且与kludgiest,ad-hoccest相比,它并没有真正需要更多的代码快捷方式! - )
答案 1 :(得分:1)
根据MVC方法的运作方式,我建议你修改你列出的第一个解决方案:
使基本目录成为窗口小部件上的实例变量,并让控制器对其进行操作以保留该窗口小部件的状态。
为什么呢?你说小部件是独立于模型的,但实际上它们是从模型中引用的吗?如果你没有将你的小部件绑定到你的模型,你就会偏离MVC的基本概念。
我对wxPython一无所知,所以我不能说它如何符合MVC,如果有的话。在我看来,你应该考虑将小部件集成到模型中,或者将它们视为模型本身。
因此,如果我们假设在这种情况下窗口小部件实际上是模型层次结构的一部分,那么这可能不仅仅是简单的解决方案,正如您所说,而是正确的解决方案。
由于MVC的核心基础之一是维护每个部分之间的松散耦合,因此您总是希望将业务逻辑与数据输入和表示隔离开来。维护与模型分开显示的小部件会破坏这一点,因此将任何类型的信息方法放入控制器都不合适。您希望模型包含在视图中显示数据时控制器需要操作或显示的所有内容。
您是否考虑为所有小部件创建一个超类,以便它们将始终继承一组通用的方法?
示例:
import os
WIDGET_PREFIX = '/tmp'
class Widget:
def __init__(self, name):
self.name = name
self.widget_prefix = WIDGET_PREFIX
self.dirs = os.walk(os.path.join(self.widget_prefix, name))
def _get_base_directory_set(self):
return self.dirs
base_directory_set = property(_get_base_directory_set)
我希望这至少能让你有所思考。
答案 2 :(得分:0)
我目前采用的解决方案是“每个小部件控制器”。 (也许它有一个现有的名称。)它委托“父”控制器用于任何UI范围的功能,但存在控制窗口小部件并基于每个窗口小部件关联任何相关数据。
“每个小部件控制器”概念避免将任何不相关的属性投射到小部件本身上。您可以扩展这些控制器,以便在创建/销毁时注册/取消注册受控小部件,以便执行多个小部件操作,从而避免弱化魔法。
例如:
class FSBrowserController(wx.EvtHandler):
"""Widget-specific controller for filesystem browsers.
:ivar parent: Parent controller -- useful when UI-wide control is
necessary to respond to an event.
"""
def __init__(self, parent, frame, tree_ctrl, base_dirs):
self.parent = parent
self.frame = frame
self.tree_ctrl = tree_ctrl
self.base_dirs = base_dirs
frame.Bind(EVT_FS_REFRESH, self.refresh)
frame.Bind(wx.EVT_WINDOW_DESTROY, self._unregister)
self.refresh()
self._register()
def _register(self):
"""Register self with parent controller."""
self.parent._fsb_controllers.append(self)
def _unregister(self, event):
"""Unregister self with parent controller."""
if event.GetEventObject() == self.frame:
self.parent._fsb_controllers.remove(self)
def refresh(self, event=None):
"""Refresh the :ivar:`tree_ctrl` using :ivar:`base_dirs`."""
raise NotImplementedError
class Controller(wx.EvtHandler):
"""Main controller for the application.
Handles UI-wide behaviors.
"""
def __init__(self):
self._fsb_controllers = []
fsb_frame = FSBrowserFrame(parent=None)
FSBrowserController(self, fsb_frame, fsb_frame.tree_ctrl,
initial_base_dirs)
fsb_frame.Show()
这样,当FSBrowserFrame被销毁时,控制器和相关数据将随之消失。