在上一个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>中。这带有共享的可变状态的所有警告,并且有点“骇人听闻”。但是,它确实可以胜任。

理想情况下,我想要一个“纯”模型,摆脱PropertyChangedObservableCollections的束缚,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),它将文档映射到控件。

10-08 03:21