我有一个问题,我可以通过使用状态的经典命令式编程轻松解决:我正在编写一个在多个节点之间共享URL的共同浏览应用程序。该程序有一个用于通信的模块,我称之为link
,用于浏览器处理,我称之为browser
。现在当一个URL到达link
时,我使用browser
模块来告诉
实际的Web浏览器开始加载URL。
实际的浏览器将触发传入URL已开始加载的导航检测,因此将立即显示为发送到另一方的候选者。必须避免这种情况,因为它会沿着以下(非常概念化的)伪代码(它的Javascript,但是请考虑一个有点无关的实现)创建一个无限循环的链接跟随到同一个URL。细节):
actualWebBrowser.urlListen.gotURL(function(url) {
// Browser delivered an URL
browser.process(url);
});
link.receivedAnURL(function(url) {
actualWebBrowser.loadURL(url); // will eventually trigger above listener
});
我首先要做的是将每个传入的网址存储在browser
中,并在网址到达时立即使用该网址,然后将其从收到的网址中删除。在browser
中列出了这一行:
browser.recents = {} // <--- mutable state
browser.recentsExpiry = 40000;
browser.doSend = function(url) {
now = (new Date).getTime();
link.sendURL(url); // <-- URL goes out on the network
// Side-effect, mutating module state, clumsy clean up mechanism :(
browser.recents[url] = now;
setTimeout(function() { delete browser.recents[url] }, browser.recentsExpiry);
return true;
}
browser.process = function(url) {
if(/* sanity checks on `url`*/) {
now = (new Date).getTime();
var duplicate = browser.recents[url];
if(! duplicate) return browser.doSend(url);
if((now - duplicate_t) > browser.recentsExpiry) {
return browser.doSend(url);
}
return false;
}
}
它有效但我对我的解决方案感到有些失望,因为我在browser
习惯性地使用了可变状态。是否有更好的方式(tm)&#34;在这种情况下使用不可变数据结构/函数式编程等?
答案 0 :(得分:0)
处理长寿状态的一种更实用的方法是将它用作递归函数的参数,并且有一个函数执行负责处理某种类型的单个“动作”,然后再用它来调用它自己。新州。
F#的MailboxProcessor
就是这种方法的一个例子。但是它确实依赖于在独立线程上进行处理,这与代码的事件驱动样式不同。
正如您所知,代码中的setTimeout
使状态管理变得复杂。您可以简化此操作的一种方法是让browser.process
过滤掉任何超时的网址,然后再执行其他操作。这也将消除对正在处理的特定URL的额外超时检查的需要。
即使您无法完全消除代码中的可变状态,也应该仔细考虑该状态的范围和生存期。
例如,您可能需要多个独立浏览器吗?如果是这样,您应该考虑如何将recents
集合封装为仅属于单个浏览器,这样您就不会发生冲突。即使您不需要多个实际应用程序,这可能有助于测试。
您可以通过多种方式将状态保持为特定浏览器的私有状态,具体取决于该语言的可用功能。例如,在具有对象的语言中,自然的方式是使其成为浏览器对象的私有成员。