我应该如何在 FRP (Rx.Net) 中对循环依赖建模?
How should I model circular dependencies in FRP (Rx.Net)?
我正在尝试通过使用 Rx.Net 来实现 Tic-Tac-Toe 来学习更多关于函数反应式编程的知识。我遇到的问题是我的游戏逻辑中似乎存在循环依赖。
commands
流(PlaceToken
、ResetGame
等)是从用户输入流生成的。
游戏的当前状态 (boardStates
) 是通过将 commands
应用于先前的状态得出的,从初始状态开始:
var initialBoardState = new BoardState();
var boardStates = commands
.Scan(initialBoardState, (boardState, command) => command.Apply(boardState))
.DistinctUntilChanged();
但是,commands
流应该依赖于 boardStates
流。这是因为有效的命令集随当前状态而变化。
例如,PlaceToken
命令应该只在用户点击一个空的图块时发出,但是空图块的集合是由当前状态定义的!
总而言之,我有两个似乎相互依赖的流。我应该如何在功能反应式编程中解决这个问题?
并不是所有的事情都需要成为一个事件。请记住 Rx/Callbacks 是一种允许您依赖的东西给您回电的方式(而不依赖于您)。
根据经验
- 循环依赖表示存在设计缺陷
- 发送命令,接收事件
2)也可以翻译成
Just call methods on your dependencies to change their state, but subscribe to their events to see their changes
因此,与其将命令视为一个流,不如将用户操作视为一个流,在收听时可能会创建一个命令。
所以 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 等)。
虽然@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
。
您唯一需要注意的地方是 运行-away 反馈循环:如果您的流从未稳定下来,那么它们就会进入无限循环!将流视为相互递归是有帮助的。
我正在尝试通过使用 Rx.Net 来实现 Tic-Tac-Toe 来学习更多关于函数反应式编程的知识。我遇到的问题是我的游戏逻辑中似乎存在循环依赖。
commands
流(PlaceToken
、ResetGame
等)是从用户输入流生成的。
游戏的当前状态 (boardStates
) 是通过将 commands
应用于先前的状态得出的,从初始状态开始:
var initialBoardState = new BoardState();
var boardStates = commands
.Scan(initialBoardState, (boardState, command) => command.Apply(boardState))
.DistinctUntilChanged();
但是,commands
流应该依赖于 boardStates
流。这是因为有效的命令集随当前状态而变化。
例如,PlaceToken
命令应该只在用户点击一个空的图块时发出,但是空图块的集合是由当前状态定义的!
总而言之,我有两个似乎相互依赖的流。我应该如何在功能反应式编程中解决这个问题?
并不是所有的事情都需要成为一个事件。请记住 Rx/Callbacks 是一种允许您依赖的东西给您回电的方式(而不依赖于您)。
根据经验
- 循环依赖表示存在设计缺陷
- 发送命令,接收事件
2)也可以翻译成
Just call methods on your dependencies to change their state, but subscribe to their events to see their changes
因此,与其将命令视为一个流,不如将用户操作视为一个流,在收听时可能会创建一个命令。
所以 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 等)。
虽然@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
。
您唯一需要注意的地方是 运行-away 反馈循环:如果您的流从未稳定下来,那么它们就会进入无限循环!将流视为相互递归是有帮助的。