不可变状态 - 有效地传播GUI的变化

时间:2013-11-11 09:51:15

标签: wpf user-interface f# immutability observer-pattern

在之前的question中,我问过如何以惯用方式为F#应用程序实现观察者模式。我的应用程序现在使用MailboxProcessor作为推荐,我已经创建了一些辅助函数来创建子邮箱处理器等。但是,当涉及特定案例场景w.r.t时,我处于精神障碍。 GUI绑定。

假设我有一个模型:

type Document = {
    Contents : seq<DocumentObject>
}

GUI(WPF,XAML)需要像这样绑定:

interface IMainWindowViewModel
{
    IEnumerable<Control> ContentViews { get; }
}

每个ViewModel的每个Control都需要DocumentObject(其基础模型)以及了解其是否已更改的方式。我将其作为子MailboxProcessor<DocumentObject>提供,以便可以正确传播更改,我对这种模式有效。本质上,它映射服务输出并包装修改请求(下面的外部接口示例):

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>。这伴随着共享可变状态的所有警告,并且有点'hackish';但它确实可以胜任。

理想情况下,我想要一个'纯'模型,不受PropertyChangedObservableCollections的限制,F#中的哪种模式可以满足这种需求?在惯用现实之间划清界线是恰当的吗?

1 个答案:

答案 0 :(得分:3)

您是否考虑过使用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,因此视图模型应该实现INPC。有不同的方法,但无论你采取哪种方法,ReactiveUI都有很多方便的工具。

例如,CreateDerivedCollection扩展方法解决了您提到的问题之一:

documents.CreateDerivedCollection(fun x -> (* ... map Document to Control ... *))

这将获取您的documents可观察集合,并从中创建另一个可观察集合(实际上是ReactiveCollection),将文档映射到控件。