在 JavaFX 中使用 ObservableList 是否违背了 Model-View-Controller 分离?

我正在尝试研究JavaFX,因为我想将其用作程序的GUI。我的问题本质上是一个概念问题:

到目前为止,我的程序主要是MVC模式的“模型”部分;也就是说,我几乎所有的代码都是类意义上的抽象的OO表示,所有这些代码都是逻辑代码。

由于我不想成为我的程序的唯一用户,我想添加MVC的“视图”部分,以便人们可以轻松使用和操作我的程序的“模型”部分。为此,我想使用JavaFX。

在我的“模型”类中,我显然使用各种列表,映射和Java集合API中的其他类。为了让我的程序的用户操作这些底层的列表和映射,我想在JavaFX中使用Observable(List/Map)接口。

一个具体的例子来澄清这种情况:

假设我有一个MachineMonitor类,每3分钟检查一次机器的某些属性,例如连接是否仍然良好,齿轮转动的速度等。如果满足某些不等式(例如齿轮的速度已降至1转/秒),MachineMonitor将触发RestartMachineEvent。

目前,我使用ArrayList<MachineMonitor>来跟踪所有单个MachineMonitor的。现在扩展到MVC的“视图”部分,我希望用户能够操作显示MachineMonitor列表的TableView,以便他们可以创建和删除新的MachineMonitor来监视各种机器。

为了让我可以跟踪程序的用户想要做什么(例如,为机器#5创建一个MachineMonitor,检查齿轮的转弯/秒是否低于0.5),我使用ObservableList<MachineMonitor>作为TableView的基础列表。

链接我的程序的“模型”和“视图”的最简单方法是简单地将“Model”类更改为具有ObservableList<MachineMonitor>而不是ArrayList<MachineMonitor>但是(进入问题的主题)我觉得这非常混乱,因为它混合了“模型”和“视图”代码。

一种幼稚的方法是对 TableView 使用 ObservableList<MachineMonitor>,并保留使用我的 ArrayList<MachineMonitor>。但是,根据 JavaFX 规范,对 ObservableList<MachineMonitor>所做的更改不会影响基础列表。

鉴于此,解决这个难题的最佳方法是为 ObservableList<MachineMonitor>制作一个 ChangeListener,它将对 ObservableList<MachineMonitor>所做的更改“传播”到底层的“模型”ArrayList<MachineMonitor>?也许把它放在一个名为MachineMonitorController的类中?

这种临时解决方案似乎非常混乱且不理想。

我的问题是:在这种情况下,保持“模型”和“视图”之间几乎完全分离的最佳方法是什么?


答案 1

简而言之,我不认为使用ObservableList会破坏MVC合同。

其余的,你可以阅读或不按照你的意愿阅读,因为它很长。

建筑模式背景

可观察量在 MVC 样式的体系结构中很有用,因为它们提供了一种通过松散耦合在 MVC 组件之间来回馈送数据的方法,其中模型和视图类不需要直接相互引用,而是可以使用一些共享数据模型来传达数据流。Observable模式和MVC风格的架构概念都起源于Xerox PARC,这并非巧合 - 事情是相互联系的。

正如 Martin Fowler 的 GUI 架构中所指出的,构建 GUI 有许多不同的方法。MVC只是其中之一,有点像他们所有人的祖父。很好地理解MVC是很好的(它经常被误解),MVC概念适用于许多地方。对于您的应用程序,您应该使用最适合您的系统,而不是严格遵循给定的模式(除非您使用的是强制给定模式的特定框架),并且还愿意在应用程序中采用不同的模式,而不是试图将所有内容硬塞进单个概念框架中。

Java Bean是几乎所有Java程序的基本组成部分。虽然传统上通常只在客户端应用程序中使用,但观察者模式(通过 )自 Java Bean 规范创建以来一直是它的一部分,这是有充分理由的。JavaFX的可观察和绑定元素是对早期工作的返工,从中学习构建更方便使用和更易于理解的东西。也许,如果 JavaFX 可观察和绑定元素在十年或十二年前作为 JDK 的一部分存在,那么与几个纯 GUI 框架相比,这些概念将更普遍地用于更广泛的库和框架中。PropertyChangeListeners

建议

我建议考虑MVVM模型和其他GUI架构。

如果你想要一个遵循模型,视图,演示者的风格的简单框架,一定要给afterburner.fx一个旋转。

我认为正确的架构选择取决于您的应用程序,您的经验以及您尝试解决的问题的大小和复杂性。例如,如果你有一个分布式系统,那么你可以遵循REST原则,而不是(或补充)MVC。无论您选择哪种方式,架构都应该帮助您解决手头的问题(以及可能的未来问题),而不是相反。过度构建解决方案是一个常见的陷阱,很容易做到,所以尽量避免它。

警告

需要考虑的一个警告是,可观察量必然通过副作用起作用,这些副作用可能难以推理,并且可能与隔离的概念相反。JavaFX具有一些很好的工具,例如ReadOnlyObjectWrapperReadOnlyListWrapper,以帮助限制对可观察量的影响(如果您愿意,可以控制损坏),以便它们不会在您的系统中疯狂运行。肆无忌惮地使用此类工具(和不可变对象)。

从示例中学习

对于使用可观察量构建的简单 JavaFX 应用程序,请参阅井字游戏

有关使用基于 FXML 的组件构建大型复杂 JavaFX 应用程序的好方法,请参阅 SceneBuilder 和 SceneBuilderKit 的源代码。源代码在JavaFX mercurial源代码树中可用,只需检查一下并开始学习即可。

阅读 JavaFX UI 控件体系结构。检查 JavaFX 控件源代码(例如 ButtonButtonSkinListViewListViewSkin),了解如何使用 JavaFX 结构应用 MVC 等概念。基于这些学习,尝试使用 JavaFX 控件框架提供的体系结构创建一些您自己的自定义控件。通常,当您构建自己的应用程序时,您不需要创建自己的控件(至少是派生自 JavaFX 控件的控件)。JavaFX控件体系结构是经过特制的,以支持构建可重用控件的库,因此它通常不一定适用于所有用途;相反,它提供了一种经过验证的方法来完成某些事情的具体演示。采用和调整经过验证的解决方案对于确保您不会不必要地重新发明东西有很长的路要走,并允许您在坚实的基础上进行构建并从他人的试验中学习。

关于您的具体示例

我建议你去:

链接程序的“模型”和“视图”的最简单方法就是将“Model”类更改为具有 ObservableList 而不是 ArrayList。

也许使用 ReadOnlyListWrapper 将 MachineMonitor 中的 ObservableList 公开给外部世界,这样就不会有任何东西可以对其进行过度修改。

设置一些封装视图的其他结构(例如 ControlPanel 和 ControlPanelSkin),并为其提供对 MachineMonitors 的只读可观察列表的引用。ControlPanelSkin可以封装TableView,图形或任何要用于用户监视机器的可视旋钮和小部件。

使用这样的结构可以有效地将您的视图与模型隔离开来。该模型实际上对UI一无所知,ControlPanelSkin的实现可以更改为完全不同的视觉表示或技术,而无需更改核心MachineMonitor系统。

上面只是概述了一种通用方法,您需要针对您的特定示例进行调整。


答案 2

我不同意在你的“模型”类中使用a违反了MVC分离。An 是纯粹的数据表示;它是模型的一部分,而不是视图的一部分。我(和其他人在应用程序的所有层的模型表示中使用JavaFX属性和集合。除此之外,我指出了我如何使用(或至少可以)绑定到JSF的JavaFX属性。(我应该提到,并不是每个人都同意在服务器端使用FX属性的方法;但是我真的没有看到任何方法可以证明它们是视图的一部分。ObservableListObservableList

另外,如果您这样做

List<MachineMonitor> myNonObservableList = ... ;

ObservableList<MachineMonitor> myObservableList = FXCollections.observableList(myNonObservableList);
myObservableList.add(new MachineMonitor());

可观察列表由不可观察列表支持,因此更改也会发生。因此,如果您愿意,可以使用此方法。myNonObservableList


推荐