反应式扩展延迟初始化



相当确定的是,在使用 SimpleInjector 解析的类型的 ctors 中做工作是一种不好的做法。尽管这通常会导致此类类型的某些延迟初始化,但反应式扩展订阅是一个特别有趣的情况。

以一个表现出Replay(1)语义的可观察序列为例(如果我们考虑StartWith,实际上是BehaviorSubject(,例如

private readonly IObservable<Value> _myObservable;
public MyType(IService service)
{
_myObservable = service.OtherObservable
.StartWith(service.Value)
.Select(x => SomeTransform())
.Replay(1)
.RefCount();
}
public IObservable<Value> MyObservable => _myObservable;

现在假设SomeTransform的计算成本很高。从SimpleInjector的角度来看,以上是不好的做法。好的,所以我们需要某种Initialize()方法在 SimpleInjector 完成后调用。但是我们的重播语义和我们的StartWith()呢?我们的消费者在Subscribe时期望一个值(现在假设这保证在初始化后发生(!

我们如何以一种很好的方式绕过这些限制,同时仍然满足SimpleInjector?以下是要求摘要:

  1. 不要在 ctor 中做大量的工作(即SomeTransform( 不应运行
  2. _myObservable应该readonly
  3. MyObservable应该表现出Replay(1)语义
  4. 我们应该始终有一个初始值(因此StartWith(
  5. 我们不想SubscribeMyType内部并缓存值(我们喜欢不变性(

我尝试创建一个额外的可观察量,该可观察量从false开始,然后在初始化时设置为true,然后将其与_myObservable合并在一起,但无法完全使其工作。此外,这似乎不是最好的解决方案。从本质上讲,我想做的只是推迟到Initialize()完成。一定有什么方法可以做到这一点,我没有看到吗?

我想到的一个简单解决方案是使用Lazy<T>

这可能看起来像:

private readonly Lazy<IObservable<Value>> _lazyMyObservable;
public MyType(IService service)
{
_lazyMyObservable =  new Lazy<IObservable<Value>>(() => this.InitObservable(service));
}
private IObservable<Value> InitObservable(IService service)
{
return service.OtherObservable
.StartWith(service.Value)
.Select(x => SomeTransform())
.Replay(1)
.RefCount();
}
public IObservable<Value> MyObservable => _lazyMyObservable.Value;

这将初始化变量_lazyMyObservable而不实际调用SomeTransform()。当消费者要求MyType.MyObservable时,InitObservable代码将被调用一次,并且只调用一次。这会将初始化推迟到实际使用代码的位置。

这将使您的构造函数保持美观和干净,并且无需添加初始化逻辑。

请注意,Lazy<T>的 ctor 有几个重载,如果多线程处理出现问题,可以使用这些重载。

注入构造函数应该简单可靠。这意味着以下做法是不受欢迎的:

  • 在构造函数内执行任何 I/O 操作。I/O 操作可能会失败,并使对象图的构造不可靠。
  • 在构造函数中使用类的依赖项。被调用的依赖项不仅会导致其自身的 I/O,有时注入的依赖项尚未(尚未(完全初始化,最终初始化发生在稍后的时间点。也许在构建了对象图之后。

考虑到反应式扩展的工作方式,您的MyType构造函数似乎不执行任何 I/O。它的SomeTransform方法在创建MyType期间不调用。相反,可观察量配置为在推送对象时调用SomeTransform。这意味着从DI的角度来看,您的注射仍然是"简单"和快速的。有时,您的类需要在存储传入依赖项的基础上进行一些初始化。例如,创建和存储Lazy<T>就是一个很好的例子。它允许延迟执行一些I/O,同时仍然拥有更多的代码,而不仅仅是"接收依赖项"。

但是您仍然在访问构造函数中的依赖项,如果该依赖项或其依赖项未完全初始化,则可能会导致麻烦。此外,使用反应式扩展,您可以创建从IServiceMyType的运行时依赖项(您已经具有从MyTypeIService的设计时依赖项(。这与在 .NET 中处理事件非常相似。这样做的后果是,即使MyTypeMyType的寿命预计会更短,IService也会

使存活。因此,严格来说,从 DI 的角度来看,这种配置可能会很麻烦。但是在使用反应式扩展时很难想象不同的模型。这意味着您必须将可观察量的此配置移出构造函数,并在构建对象图之后执行此操作。但这可能会导致必须打开您的类,以便组合根可以访问需要调用的方法。它还会导致时间耦合。

换句话说,在使用反应式扩展时,最好有一些设计规则来防止出现问题。这些规则可以是:

  • 所有公开的IObservable<T>属性都应始终完全初始化,并在其类型构造后可用。
  • 所有观察器和可观察量应具有相同的生存期。

相关内容

  • 没有找到相关文章

最新更新