我应该如何在FRP(Rx.Net)中建模循环依赖?

时间:2016-07-12 16:03:53

标签: c# system.reactive reactive-programming circular-dependency rx.net

我试图通过使用Rx.Net来实现Tic-Tac-Toe来学习更多关于功能反应式编程的知识。我遇到的问题是我的游戏逻辑似乎存在循环依赖。

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

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

var initialBoardState = new BoardState();

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

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

例如,PlaceToken命令只应在用户点击空图块时发出,但空图块集由当前状态定义!

总而言之,我有两个似乎依赖于彼此的流。我应该如何在功能反应式编程中解决这个问题?

2 个答案:

答案 0 :(得分:2)

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

问题在于我们有一个循环。行动流取决于董事会状态流,而这又依赖于行动流:

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

cycle.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

唯一需要注意的地方是逃避反馈循环:如果你的流不会稳定,那么它们会无限循环!将流视为相互递归是有帮助的。

答案 1 :(得分:1)

并非一切都需要成为一个事件。记住Rx / Callbacks是一种允许你依赖的东西,给你回电话(不依赖你)。

根据经验

  1. 循环依赖表示设计缺陷
  2. 发送命令,接收事件
  3. 2)也可以翻译为

      

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

    因此,您不应将Commands视为流,而应将用户操作视为流,当收听时可能会创建命令。

    所以BoardState可能看起来像这样

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

    并且ViewModel(?)代码可能看起来像这样

    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等)。