我应该如何在FRP(Rx.Net)中对循环依赖关系进行建模

本文关键字:依赖 循环 关系 建模 FRP Net Rx 我应该 | 更新日期: 2023-09-27 17:56:30

我正在尝试通过使用 Rx.Net 来实现井字游戏来了解有关函数式反应式编程的更多信息。我遇到的问题是,我的游戏逻辑中似乎存在循环依赖关系。

commands流(PlaceTokenResetGame等)是从用户输入流生成的。

游戏的当前状态(boardStates)是通过将commands应用于先前的状态得出的,从初始状态开始:

var initialBoardState = new BoardState();
var boardStates = commands
    .Scan(initialBoardState, (boardState, command) => command.Apply(boardState))
    .DistinctUntilChanged();

但是,commands流应取决于boardStates流。这是因为有效的命令集随当前状态而变化。

例如,PlaceToken命令应仅在用户单击空磁贴时发出,但空磁贴集由当前状态定义!

总而言之,我有两个流似乎相互依赖。我应该如何在函数式反应式编程中解决这个问题?

我应该如何在FRP(Rx.Net)中对循环依赖关系进行建模

虽然@LeeCampbell的解决方案确实有效,但它需要使核心模型类可变。相反,我发现最好复制周期.js采取的方法。你可以在这里看到他们的解释。

问题是我们有一个周期。操作流取决于板状态流,而板状态流又取决于操作流:

boardStream = f(actionStream)
actionStream = g(boardStream)

循环.js解决方案是使用代理流将所有内容连接在一起:

// Create a proxy
proxyActionStream = new Stream()
// Create our mutually dependent streams using the proxy
boardStream = f(proxyActionStream)
actionStream = g(boardStream)
// Feed actionStream back into proxyActionStream
actionStream.Subscribe(x => proxyActionStream.OnNext(x))

在 Rx.Net 土地上,代理流应该是ReplaySubject

您唯一需要注意的地方是失控的反馈循环:如果您的流永远不会稳定,那么它们就会遇到无限循环!将流视为相互递归是有帮助的。

不是所有的事情都需要是一个事件。请记住,Rx/回调是一种允许你依赖的东西回电(不依赖你)的方式。

根据经验

  1. 循环依赖关系表示存在设计缺陷
  2. 发送命令,接收事件

2)也可以翻译成

只需调用依赖项上的方法即可更改其状态,但订阅其事件即可查看其更改

因此,不应将命令视为流

,而应将用户操作视为在侦听时可能会创建命令的流。

所以董事会状态可能看起来像这样

public class BoardState
{
    public void PlaceToken(PlaceTokenCommand placeToken)
    {
        //Process, then raise event
    }
    public void Reset()
    {
        //Process, then raise event
    }
    public IObservable<?> StateUpdates()
    {
    }
}

视图模型(?)代码可能看起来像这样

public class TicTacToeViewModel
{
    private readonly BoardState _board;
    public TicTacToeViewModel()
    {
        _board = new BoardState();
        MoveTokenCommand = new DelegateCommand(MoveToken, CanMoveToken);
        ResetBoardCommand = new DelegateCommand(_board.Reset);
        board.StateUpdates(state => UpdatePresentation(state));
    }
    public DelegateCommand MoveTokenCommand { get; private set;}
    public DelegateCommand ResetBoardCommand { get; private set;}

    private void MoveToken()
    {
        var token = CurrentToken;
        var location = ActiveLocation;
        var cmd = new PlaceTokenCommand(token, location);
        _board.PlaceToken(cmd);
    }
    private bool CanMoveToken()
    {
        //?
    }
}

但正如@Enigmativity的那样,评论中的请求,如果没有 MCVE,很难提供明智的帮助。

最后要注意的是,虽然Rx是功能性的并且是反应性的,但狂热者会反对将Rx视为FRP(参见Conal,Behaviors等)。