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

Ada*_*ley 6 wpf user-interface f# immutability observer-pattern

在上一个问题中,我问过如何习惯性地为F#应用程序实现一个观察者模式.我的应用程序现在使用MailboxProcessor作为推荐,我已经创建了一些辅助函数来创建子邮箱处理器等.但是,当涉及到GUI绑定的特定案例场景时,我处于心理障碍.

让我们说我有一个模型:

type Document = {
    Contents : seq<DocumentObject>
}
Run Code Online (Sandbox Code Playgroud)

GUI(WPF,XAML)需要绑定,如下所示:

interface IMainWindowViewModel
{
    IEnumerable<Control> ContentViews { get; }
}
Run Code Online (Sandbox Code Playgroud)

每个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)
Run Code Online (Sandbox Code Playgroud)

现在,想象一个修改命令现在删除DocumentObjectMyDocument.顶级MailboxProcessor现在回应IMainWindowViewModel使用它的变化IEvent<MyDocument>.这就是我的问题开始的地方.

IMainWindowViewModel真的不知道 哪个DocumentObject被删除了.只有那是一个新的Document,它必须处理它.可能有一些方法可以搞清楚,但它从来没有直接知道.这可以迫使我倒不必任何的路径重新创建所有Control的所有DocumentObject的是安全的(低效率).还有其他问题(例如悬空subSvc),为简洁起见,我在这里也没有提到.

通常情况下,这些动态变化会被处理成类似的东西ObservableCollection<DocumentObject>然后被映射到一个ObservableCollection<Control>.这伴随着共享可变状态的所有警告,并且有点'hackish'; 然而,它确实做到了.

理想情况下,我想要一个"纯粹"的模式,从的派头自由PropertyChangedObservableCollections有什么样的F#中的模式将满足这方面的需求?在惯用语现实之间画出界线是恰当的吗?

Mis*_*hor 4

您是否考虑过使用响应式扩展(以及未来的响应式 UI )以功能性方式对可变状态进行建模(阅读:随时间变化的模型属性)?

我认为在您的模型中使用 an 在技术上没有任何问题ObservableCollection。毕竟,您需要跟踪集合更改。您可以自己完成,但看起来您可以为自己节省很多重新发明可观察集合的麻烦,除非您有非常具体的理由避免使用该类ObservableCollection

另外,使用MailboxProcessor似乎有点矫枉过正,因为您可以使用 a 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)
Run Code Online (Sandbox Code Playgroud)

当然,由于我们讨论的是 WPF,因此视图模型应该实现INPC. 有不同的方法,但无论您采用哪一种,ReactiveUI都有很多方便的工具。

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

documents.CreateDerivedCollection(fun x -> (* ... map Document to Control ... *))
Run Code Online (Sandbox Code Playgroud)

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

  • 嘿,非常感谢您的回答。正如我一直在阅读的那样,您的方法似乎是最现实的。理想情况下,我*喜欢*拥有一个干净的模型,没有任何 GUI 考虑,但似乎所有工具(“Rx”等)都是针对它的。我有点想在这里鱼与熊掌兼得,这是一场艰苦的战斗。感谢您的贡献。 (2认同)