3

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

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

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

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

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

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

4

1 回答 1

6

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

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

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

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

这将这些责任留给了模型:

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

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

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

如果您使用的是 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 是一个模型控制器和视图控制器,针对其特定任务进行了本地化。

于 2013-01-10T18:41:20.157 回答