我维护一个用于金属切割机的 CAD/CAM 软件。所以我对这个问题有一些经验。
当我们第一次将我们的软件(它于 1985 年首次发布!)转换为面向对象设计时,我做了你不喜欢的事情。对象和接口有 Draw、WriteToFile 等。在转换过程中发现和阅读设计模式有很大帮助,但仍然有很多不好的代码气味。
最终我意识到这些类型的操作都不是对象真正关心的。而是需要执行各种操作的各种子系统。我通过使用现在所谓的被动视图命令对象以及软件层之间定义良好的接口来处理这个问题。
我们的软件结构基本上是这样的
- 实现各种表单接口的表单。这些表单是将事件传递给 UI 层的事物外壳。
- UI 层,通过 Form 接口接收事件和操作表单。
- UI层将执行所有实现Command接口的命令
- UI 对象有自己的接口,命令可以与之交互。
- 命令获取他们需要的信息,对其进行处理,操作模型,然后向 UI 对象报告,然后 UI 对象对表单执行任何所需的操作。
- 最后是包含我们系统的各种对象的模型。像形状程序、切割路径、切割台和金属板。
所以绘图是在 UI 层中处理的。我们为不同的机器提供不同的软件。因此,尽管我们所有的软件都共享相同的模型并重复使用许多相同的命令。他们处理绘画之类的事情非常不同。例如,对于路由器机器和使用等离子炬的机器来说,切割台的绘制是不同的,尽管它们本质上都是一个巨大的 XY 平板。这是因为就像汽车一样,这两种机器的制造方式不同,因此给客户带来了视觉上的差异。
至于形状我们做的如下
我们有通过输入参数生成切割路径的形状程序。切割路径知道产生了哪个形状程序。然而,切割路径不是形状。它只是在屏幕上绘制和切割形状所需的信息。这种设计的一个原因是当从外部应用程序导入切割路径时,可以在没有形状程序的情况下创建切割路径。
这种设计使我们能够将切割路径的设计与形状的设计分开,这并不总是相同的东西。在您的情况下,您可能需要打包的只是绘制形状所需的信息。
每个形状程序都有许多实现 IShapeView 接口的视图。通过 IShapeView 接口,形状程序可以告诉我们如何设置自己的通用形状表单以显示该形状的参数。通用形状表单实现一个 IShapeForm 接口并将其自身注册到 ShapeScreen 对象。ShapeScreen 对象向我们的应用程序对象注册自身。形状视图使用向应用程序注册自身的任何形状屏幕。
我们有客户喜欢以不同方式输入形状的多重视图的原因。我们的客户群分为喜欢以表格形式输入形状参数的人和喜欢在他们面前以图形表示形式输入的人。我们有时还需要通过最小的对话框而不是完整的形状输入屏幕来访问参数。因此,多个视图。
操纵形状的命令属于两种类别之一。他们要么操纵切割路径,要么操纵形状参数。为了操纵形状参数,我们通常要么将它们放回形状输入屏幕,要么显示最小对话框。重新计算形状,并将其显示在同一位置。
对于切割路径,我们将每个操作捆绑在一个单独的命令对象中。例如我们有命令对象
ResizePath RotatePath MovePath SplitPath 等等。
当我们需要添加新功能时,我们添加另一个命令对象,在右侧 UI 屏幕中找到菜单、键盘短键或工具栏按钮槽,并设置 UI 对象来执行该命令。
例如
CuttingTableScreen.KeyRoute.Add vbShift+vbKeyF1, New MirrorPath
或者
CuttingTableScreen.Toolbar("Edit Path").AddButton Application.Icons("MirrorPath"),"Mirror Path", New MirrorPath
在这两种情况下,Command 对象 MirrorPath 都与所需的 UI 元素相关联。在 MirrorPath 的执行方法中是镜像特定轴上的路径所需的所有代码。该命令可能会有它自己的对话框或使用其中一个 UI 元素来询问用户要镜像哪个轴。这些都不是访问者,也不是向路径添加方法。
您会发现通过将操作捆绑到命令中可以处理很多事情。但是我警告说,这不是非黑即白的情况。您仍然会发现某些东西作为原始对象的方法效果更好。在可能的经验中,我发现我过去在方法中所做的事情可能有 80% 可以移到命令中。最后 20% 只是简单地在对象上工作得更好。
现在有些人可能不喜欢这样,因为它似乎违反了封装。从过去十年将我们的软件维护为面向对象的系统,我不得不说,从长远来看,您可以做的最重要的事情是清楚地记录软件不同层之间以及不同对象之间的交互。
将动作捆绑到 Command 对象中有助于实现这一目标,而不是盲目地致力于封装的理想。镜像路径所需的一切都捆绑在镜像路径命令对象中。