在上一个question中,我询问了如何为F#应用程序惯用地实现观察者模式。我的应用程序现在使用推荐的MailboxProcessor,并且我已经创建了一些帮助函数来创建sub-MailboxProcessor的类。但是,在涉及特定案例场景时,我陷入了困境。 GUI绑定(bind)。
可以说我有一个这样的模型:
type Document = {
Contents : seq<DocumentObject>
}
GUI(WPF,XAML)需要这样的绑定(bind):
interface IMainWindowViewModel
{
IEnumerable<Control> ContentViews { get; }
}
每个
ViewModel
的每个Control
将需要一个DocumentObject
(其基础模型)以及一种知道其是否已更改的方式。我将其作为子MailboxProcessor<DocumentObject>
提供,以便可以正确传播更改,我对此模式一定有信心。本质上,它映射服务输出并包装修改请求(下面的外部接口(interface)示例):let subSvc = generateSubSvc svc (fun doc -> doc.Contents[0]) (fun f -> fun oldDoc -> { oldDoc with Contents[0] = f Contents[0] })
let viewModel = new SomeDocObjViewModel(docObjSvc)
new DocObjView(viewModel)
现在,想象一下修改命令现在从
DocumentObject
中删除了MyDocument
。现在,顶层MailboxProcessor
使用IMainWindowViewModel
将更改回显到IEvent<MyDocument>
。这就是我的问题开始的地方。我的
IMainWindowViewModel
确实不知道哪个DocumentObject
已被删除。只是有一个新的Document
,它必须处理它。可能有一些方法可以弄清楚,但它从来没有真正真正知道过。这可能会迫使我沿着必须重新创建所有Control
的道路,以确保所有DocumentObject
的安全(低效)。还有一些其他问题(例如,悬挂的subSvc
),为简洁起见,在这里也没有提到。通常,这类动态变化将通过
ObservableCollection<DocumentObject>
之类的东西处理,然后将其映射到ObservableCollection<Control>
中。这带有共享的可变状态的所有警告,并且有点“骇人听闻”。但是,它确实可以胜任。理想情况下,我想要一个“纯”模型,摆脱
PropertyChanged
和ObservableCollections
的束缚,F#中的哪种模式可以满足此需求?在习惯用法和现实之间划清界限的适当位置是什么? 最佳答案
您是否考虑过使用Reactive Extensions(以及更远的Reactive UI)以功能化的方式对可变状态建模(请参阅:随着时间的推移,您的模型属性)?
从技术上讲,我看不到在模型中使用ObservableCollection
的任何错误。毕竟,您需要跟踪集合更改。您可以自己完成此操作,但是看起来您可以为重新创建可观察的集合省去很多麻烦,除非您有非常特殊的理由要避免使用ObservableCollection
类。
另外,使用MailboxProcessor
似乎有点过大,因为您可以使用Subject
(来自Rx)发布并将其作为IObservable
公开以订阅“消息”:
type TheModel() =
let charactersCountSubject = new Subject()
let downloadDocument (* ... *) = async {
let! text = // ...
charactersCountSubject.OnNext(text.Length)
}
member val CharactersCount = charactersCountSubject.AsObservable() with get
type TheViewModel(model : TheModel) =
// ...
member val IsTooManyCharacters = model.CharactersCount.Select((>) 42)
当然,由于我们在谈论WPF,因此 View 模型应实现
INPC
。有不同的方法,但是无论您采用哪种方法,ReactiveUI
都有许多方便的工具。例如,
CreateDerivedCollection
扩展方法解决了您提到的问题之一:documents.CreateDerivedCollection(fun x -> (* ... map Document to Control ... *))
这将获取您的
documents
可观察的集合,并从中收集另一个可观察的集合(实际上是ReactiveCollection
),它将文档映射到控件。