我应该如何在FRP(Rx.Net)中对循环依赖关系进行建模
本文关键字:依赖 循环 关系 建模 FRP Net Rx 我应该 | 更新日期: 2023-09-27 17:56:30
我正在尝试通过使用 Rx.Net 来实现井字游戏来了解有关函数式反应式编程的更多信息。我遇到的问题是,我的游戏逻辑中似乎存在循环依赖关系。
commands
流(PlaceToken
、ResetGame
等)是从用户输入流生成的。
游戏的当前状态(boardStates
)是通过将commands
应用于先前的状态得出的,从初始状态开始:
var initialBoardState = new BoardState();
var boardStates = commands
.Scan(initialBoardState, (boardState, command) => command.Apply(boardState))
.DistinctUntilChanged();
但是,commands
流应取决于boardStates
流。这是因为有效的命令集随当前状态而变化。
例如,PlaceToken
命令应仅在用户单击空磁贴时发出,但空磁贴集由当前状态定义!
总而言之,我有两个流似乎相互依赖。我应该如何在函数式反应式编程中解决这个问题?
虽然@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/回调是一种允许你依赖的东西回电(不依赖你)的方式。
根据经验
- 循环依赖关系表示存在设计缺陷
- 发送命令,接收事件
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等)。