将方法调用变成可观察事件,好主意吗?
Turning a method call into an observable event, good idea?
我正在学习如何将 IObservable
合并到我的代码中。下面是一个简单的 class 的两种不同方法,它打印出 IObservable<string>
中的最新单词。哪种方法更清洁?我不喜欢 WordPrinterWithCache
,因为它引入了额外的状态(_lastWord
)变量,有趣的代码现在分散在整个 class 中。我更喜欢 WordPrinterWithExtraSubject
因为有趣的代码已本地化到构造函数。但大多数情况下,它似乎更稳定地发挥作用并且 "reactive";我对两个 "events" 的组合做出反应:(1) 发出一个新词,以及 (2) PrintMostRecent
方法的调用。
但是我读到,在并非绝对必要时使用 Subjects 是不可取的,我正在引入一个不必要的 Subject<Unit>
。本质上,我在这里所做的是从方法调用中生成一个可观察对象,这样我就可以在更多地方使用可观察对象组合器函数。我喜欢使用可观察组合器和订阅而不是使用 "old-style" 方法调用树来构建我的代码的想法。我不知道这是不是个好主意。
public class WordPrinterWithCache
{
string _lastWord = string.Empty;
public WordPrinterWithCache(IObservable<string> words)
{
words.Subscribe(w => _lastWord = w);
}
public void PrintMostRecent() => Console.WriteLine(_lastWord);
}
public class WordPrinterWithExtraSubject
{
Subject<Unit> _printRequest = new Subject<Unit>();
public WordPrinterWithExtraSubject(IObservable<string> words)
{
_printRequest
.WithLatestFrom(words.StartWith(string.Empty), (_, w) => w)
.Subscribe(w => Console.WriteLine(w));
}
public void PrintMostRecent() => _printRequest.OnNext(Unit.Default);
}
我代码中的实际情况是我有一个 ICommand
。当用户调用命令(可能通过单击按钮)时,我想对特定可观察对象的 最新值 采取行动。例如,ICommand
可能代表 Delete
,而我想删除列表中由 IObservable<Guid>
表示的所选项目。为每个可观察对象保留一堆 "last emitted value" 缓存变量变得很丑陋。
我倾向于的方法是 ICommand
实现,类似于您在下面看到的。这允许我编写像 deleteCommand.WithLatestFrom(selectedItems,(d,s)=>s).Subscribe(selected=>delete(selected));
这样的代码
public class ObservableCommand : ICommand, IObservable<object>
{
bool _mostRecentCanExecute = true;
Subject<object> _executeRequested = new Subject<object>();
public ObservableCommand(IObservable<bool> canExecute)
{
canExecute.Subscribe(c => _mostRecentCanExecute = c);
}
public event EventHandler CanExecuteChanged; // not implemented yet
public bool CanExecute(object parameter) => _mostRecentCanExecute;
public void Execute(object parameter) => _executeRequested.OnNext(parameter);
public IDisposable Subscribe(IObserver<object> observer) => _executeRequested.Subscribe(observer);
}
虽然我不确定我是否会使用术语 "turning a method call into an observable event",但我一直是使用完全声明式、函数式和 Rx 驱动代码的粉丝。
您对 ICommand 实现的描述与我几年前写的非常接近,此后我使用了很多次并且非常成功。此外,这已成为我称为 "Reactive Behaviors" 的模式的基础,它提供了许多好处。来自我的博客:
- Promotes behavior driven development and unit testing.
- Promotes functional and thread safe programming practises.
- Reduces the risk of (and if done well, can eliminate) side effects as specific behaviors are isolated in a single well named method.
- Stops 'code rot' as all behavior is encapsulated within specifically named methods. Want new behaviour? Add a new method. Don't want a specific behavior anymore? Just removed it. Want a specific behavior to change? Change the one method and know that you haven't broken anything else.
- Provides concise mechanisms for aggregating multiple inputs and promotes asynchronous processes to first-class status.
- Reduces the need for utility classes as data can be passed through the pipeline as strongly typed anonymous classes.
- Prevents memory leaks as all behaviors return a disposable that when disposed removes all subscriptions and disposed all managed resources.
您可以阅读全文on my blog。
因此,作为一般规则(准则),我强烈建议不要将 IObservable<T>
作为方法的参数。明显的警告是,如果该方法是一个新的 Rx 运算符,例如Select
、MySpecialBuffer
、Debounce
等
这里的理论是IObservable<T>
是一种回调机制。它允许某些东西回调到另一个它一无所知的东西。但是在这种情况下,您知道 IObservable<T>
(参数)和其他东西(WordPrinterWithCache
)。
那么为什么会有这个额外的间接层呢?将值推送到 IObservable<T>
的到底是什么,可以改为调用 WordPrinterWithCache
实例的方法。
在这种情况下,只需调用另一个方法
public class WordPrinterWithCache
{
private string _lastWord = string.Empty;
public void SetLastWord(string word)
{
_lastWord = word;
}
public void PrintMostRecent() => Console.WriteLine(_lastWord);
}
现在这个 class 开始看起来毫无意义,但这可能没问题。简单就好。
使用 Rx 来帮助您分层。
上游层依赖于下游层。他们将直接调用下游层的方法(发出命令)。
下游层将无法访问上游层。因此,要向他们公开数据,他们可以 return 来自方法的值,或者可以公开回调。 GoF Observer 模式、.NET 事件和 Rx 是向上游层提供回调的方法。
我正在学习如何将 IObservable
合并到我的代码中。下面是一个简单的 class 的两种不同方法,它打印出 IObservable<string>
中的最新单词。哪种方法更清洁?我不喜欢 WordPrinterWithCache
,因为它引入了额外的状态(_lastWord
)变量,有趣的代码现在分散在整个 class 中。我更喜欢 WordPrinterWithExtraSubject
因为有趣的代码已本地化到构造函数。但大多数情况下,它似乎更稳定地发挥作用并且 "reactive";我对两个 "events" 的组合做出反应:(1) 发出一个新词,以及 (2) PrintMostRecent
方法的调用。
但是我读到,在并非绝对必要时使用 Subjects 是不可取的,我正在引入一个不必要的 Subject<Unit>
。本质上,我在这里所做的是从方法调用中生成一个可观察对象,这样我就可以在更多地方使用可观察对象组合器函数。我喜欢使用可观察组合器和订阅而不是使用 "old-style" 方法调用树来构建我的代码的想法。我不知道这是不是个好主意。
public class WordPrinterWithCache
{
string _lastWord = string.Empty;
public WordPrinterWithCache(IObservable<string> words)
{
words.Subscribe(w => _lastWord = w);
}
public void PrintMostRecent() => Console.WriteLine(_lastWord);
}
public class WordPrinterWithExtraSubject
{
Subject<Unit> _printRequest = new Subject<Unit>();
public WordPrinterWithExtraSubject(IObservable<string> words)
{
_printRequest
.WithLatestFrom(words.StartWith(string.Empty), (_, w) => w)
.Subscribe(w => Console.WriteLine(w));
}
public void PrintMostRecent() => _printRequest.OnNext(Unit.Default);
}
我代码中的实际情况是我有一个 ICommand
。当用户调用命令(可能通过单击按钮)时,我想对特定可观察对象的 最新值 采取行动。例如,ICommand
可能代表 Delete
,而我想删除列表中由 IObservable<Guid>
表示的所选项目。为每个可观察对象保留一堆 "last emitted value" 缓存变量变得很丑陋。
我倾向于的方法是 ICommand
实现,类似于您在下面看到的。这允许我编写像 deleteCommand.WithLatestFrom(selectedItems,(d,s)=>s).Subscribe(selected=>delete(selected));
public class ObservableCommand : ICommand, IObservable<object>
{
bool _mostRecentCanExecute = true;
Subject<object> _executeRequested = new Subject<object>();
public ObservableCommand(IObservable<bool> canExecute)
{
canExecute.Subscribe(c => _mostRecentCanExecute = c);
}
public event EventHandler CanExecuteChanged; // not implemented yet
public bool CanExecute(object parameter) => _mostRecentCanExecute;
public void Execute(object parameter) => _executeRequested.OnNext(parameter);
public IDisposable Subscribe(IObserver<object> observer) => _executeRequested.Subscribe(observer);
}
虽然我不确定我是否会使用术语 "turning a method call into an observable event",但我一直是使用完全声明式、函数式和 Rx 驱动代码的粉丝。
您对 ICommand 实现的描述与我几年前写的非常接近,此后我使用了很多次并且非常成功。此外,这已成为我称为 "Reactive Behaviors" 的模式的基础,它提供了许多好处。来自我的博客:
- Promotes behavior driven development and unit testing.
- Promotes functional and thread safe programming practises.
- Reduces the risk of (and if done well, can eliminate) side effects as specific behaviors are isolated in a single well named method.
- Stops 'code rot' as all behavior is encapsulated within specifically named methods. Want new behaviour? Add a new method. Don't want a specific behavior anymore? Just removed it. Want a specific behavior to change? Change the one method and know that you haven't broken anything else.
- Provides concise mechanisms for aggregating multiple inputs and promotes asynchronous processes to first-class status.
- Reduces the need for utility classes as data can be passed through the pipeline as strongly typed anonymous classes.
- Prevents memory leaks as all behaviors return a disposable that when disposed removes all subscriptions and disposed all managed resources.
您可以阅读全文on my blog。
因此,作为一般规则(准则),我强烈建议不要将 IObservable<T>
作为方法的参数。明显的警告是,如果该方法是一个新的 Rx 运算符,例如Select
、MySpecialBuffer
、Debounce
等
这里的理论是IObservable<T>
是一种回调机制。它允许某些东西回调到另一个它一无所知的东西。但是在这种情况下,您知道 IObservable<T>
(参数)和其他东西(WordPrinterWithCache
)。
那么为什么会有这个额外的间接层呢?将值推送到 IObservable<T>
的到底是什么,可以改为调用 WordPrinterWithCache
实例的方法。
在这种情况下,只需调用另一个方法
public class WordPrinterWithCache
{
private string _lastWord = string.Empty;
public void SetLastWord(string word)
{
_lastWord = word;
}
public void PrintMostRecent() => Console.WriteLine(_lastWord);
}
现在这个 class 开始看起来毫无意义,但这可能没问题。简单就好。
使用 Rx 来帮助您分层。
上游层依赖于下游层。他们将直接调用下游层的方法(发出命令)。
下游层将无法访问上游层。因此,要向他们公开数据,他们可以 return 来自方法的值,或者可以公开回调。 GoF Observer 模式、.NET 事件和 Rx 是向上游层提供回调的方法。