【问题标题】:Cocoa MVC: interaction between "model controller" and "view controller"Cocoa MVC:“模型控制器”和“视图控制器”之间的交互
【发布时间】:2013-01-10 16:44:54
【问题描述】:

在 BNR 的 Cocoa Programming for Mac OS X (4th Ed.) 的帮助下刚开始学习 Objective-C 和 Cocoa,我正在开发一个基于文档的应用程序。我已经阅读了有关文档架构的 Apple 开发人员文档,并选择在我的 NSDocument 子类上继承 NSWindowController 并覆盖 makeWindowControllers。我这样做有几个原因:

  • 将模型逻辑(在NSDocument 子类中)与视图逻辑(在NSWindowController 子类中)分开。
  • 自定义我的文档窗口的标题(Apple 的开发人员文档说,在没有不必要的副作用的情况下执行此操作的正确方法是继承 NSWindowController 并覆盖 windowTitleForDocumentDisplayName:
  • Apple 的文档似乎强烈建议将 NSWindowController 子类化,但最简单的应用程序除外,而我的应用程序绝对不是“简单”

所以,我的NSDocument 子类是一个模型控制器,而我的NSWindowController 子类是一个视图控制器。此外,我了解应用程序的大部分“工作”都是在控制器对象中完成的,因为视图和模型应该尽可能与应用程序无关且可重用。现在我的问题来了:这两种类型的控制器如何交互以实际完成这项“工作”?

例如,假设我正在编写一个电子表格应用程序,并且我想要有一个菜单项(或工具栏按钮),它会调出一个工作表,用于根据我的一些数据创建图表或图形。在该表中,用户将输入各种参数和选项来创建图表或图形,然后单击“确定”(或任何按钮)。

谁应该响应菜单项的操作,文档(模型控制器)还是窗口控制器(视图控制器)?实际加载和显示工作表的任务似乎绝对是“与视图相关的”,所以它应该放在窗口控制器中,对吗?但是工作表的控制器需要一个模型来向用户显示(Chart 对象,或者可能是ChartInputs);该模型在哪里创建并提供给工作表控制器?文档是否应该通过创建ChartInputs 模型对象来响应菜单项,然后将其传递给窗口控制器,后者创建工作表控制器,将模型对象传递给它,并显示工作表?或者窗口控制器是否应该响应菜单项,请求一个新的模型对象(可能通过依赖注入到窗口控制器的构造函数中提供的某种工厂),然后继续创建工作表控制器,传递模型并显示表格?

用户填写表格并点击“确定”后会怎样?应该将控制返回到哪里来处理用户的选择并实际创建图表——窗口控制器、文档或两者兼而有之?在用户单击“确定”之后但在关闭工作表之前(以防某些内容无效)验证用户输入的逻辑呢?

【问题讨论】:

    标签: macos cocoa model-view-controller


    【解决方案1】:

    首先,考虑您的 NSDocument 的无窗口操作。例如,您可以创建一个实用程序应用程序,它共享您的 NSDocument 类,打开文档以进行脚本、打印或其他操作,但不显示您的主文档窗口。想象一下您的 NSDocument 类为该应用程序重用 - 并将您不想要的逻辑放入您的窗口控制器。这样,NSDocument 子类主要负责影响文档状态的活动。

    这些是模型控制器(NSDocument 子类)的职责:

    • 序列化和反序列化
    • 加载和保存
    • 处理文档的状态
    • 管理和调度打印视图
    • 监控文档是否被其他人更改
    • 刷新支持数据源(影响文档模型的源)并根据需要对模型和文档应用更改
    • 管理与文档相关的活动日志
    • 拥有撤消管理器
    • 向视图控制器公开基本模型对象
    • 创建窗口控制器
    • 促进某些编辑行为,例如更改某个属性以触发创建或删除对象

    如果您使用 Core Data,您的托管对象上下文、持久存储协调器和持久存储是模型控制器的一部分,而不是模型。当然,托管对象本身也是一部分模型。

    这将这些责任留给模型:

    • 用于插入、重新排列和删除模型对象的辅助方法
    • 用于访问模型特定部分的辅助方法
    • 数据验证
    • 将模型呈现为各种格式的字符串
    • 对自身进行序列化和反序列化
    • 促进某些编辑行为,例如对一个属性的更改触发对其他属性的更改

    另一方面,这些是视图控制器的职责:

    • 操作视图以使其与模型保持同步
    • 操作视图以使其与文档状态保持同步
    • 响应本地化操作,例如添加或删除模型对象的按钮
    • 呈现辅助视图并响应这些视图中的输入
    • 调度受 UI 中的选择影响的操作,例如表格视图中的选定行
    • 管理编辑过程中使用的与文档本身无关的辅助控制器(如网络服务数据)
    • 作为视图对象的数据源

    如果您使用的是 Cocoa 绑定,那么您的绑定也是视图控制器的一部分。

    这种设计在视图控制器和模型控制器之间产生了合理的职责分离。但是,它们位于视图和模型之间。虽然这会产生模块化,但不会产生解耦。

    虽然我确实考虑过无窗口操作,但我在很大程度上是凭经验得出了这种设计模式——通过将相似的代码放在一起并将感觉不合适的代码分开。我很好奇其他人是否发布了同意或不同意如何执行此操作的权威来源或参考资料。


    以您为例,我建议采用以下设计:

    1. EditorWindowController 创建 ChartParameters 对象并为其提供对文档模型的引用:

      [[ChartParameters alloc] initWithWorkbook:self.document.workbook]
      
    2. EditorWindowController 设置新图表视图,它可能有自己的 NewChartViewController。它将 ChartParameters 和文档对象传递给 NewChartViewController 并显示窗口。

    3. ChartParameters 对象负责验证用户的选择。 NewChartViewController 需要操作视图以使其与验证结果保持同步。 (避免让用户犯错误:不要等到最后才验证输入。)

    4. 当视图结束时,NewChartViewController 要求模型使用给定的参数创建一个新图表:

      [self.document.workbook addChartWithParameters:self.chartParameters]
      

    如果您希望 not-yet-a-chart 对象成为文档的一部分,您可以这样做:

    1. EditorWindowController 要求文档模型创建一个新的图表对象:

      Chart *newChart = [self.document.workbook addChart]
      
    2. 新图表应该设置一个标志,表明它还没有准备好显示。

    3. EditorWindowController 设置 NewChartViewController,将图表传递给它,显示窗口。

    4. Chart 对象验证用户的选择,NewChartViewController 使视图保持同步。

    5. 完成后,告诉图表它已准备好显示。或者,如果用户取消,请将其删除。

    在这两种设计中,NewChartViewController 是一个模型控制器和视图控制器,并针对其特定任务进行了本地化。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多