被动扩展延迟初始化

问题描述 投票:0回答:2

相当确定,对于使用SimpleInjector解析的类型在ctor中进行操作是不好的做法。尽管这通常会导致此类类型的某些后期初始化,但特别有趣的情况是Reactive Extensions订阅。

例如,以具有Replay(1)语义的可观察序列(例如,如果我们考虑BehaviorSubject则为StartWith),例如

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的角度来看,上述做法是错误的做法。好的,所以我们需要在SimpleInjector完成后调用某种Initialize()方法。但是我们的重放语义和StartWith()呢?我们的消费者在Subscribe时期望有一个值(假设现在保证在初始化之后会发生这种情况)!

我们如何在仍然满足SimpleInjector的同时很好地克服这些限制?以下是要求摘要:

  1. 不要在ctor中做大量工作(即SomeTransform),应该不运行
  2. [_myObservable应该是readonly
  3. [MyObservable应该表现出Replay(1)语义]
  4. 我们应该始终具有初始值(因此,StartWith
  5. 我们不想在Subscribe内放入MyType并缓存值(我们喜欢不变性)

我尝试创建一个以false开头的附加可观察对象,然后在初始化时将其设置为true,然后将其与_myObservable合并在一起,但并不能完全起作用。此外,它似乎不是最佳解决方案。本质上,我只想延迟Initialize()完成。必须有某种我看不到的方法?

c# system.reactive simple-injector
2个回答
2
投票

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

这可能看起来像:

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的ctor有多个重载,如果您可能遇到多线程问题,则可以使用。


0
投票

注入构造函数应为Lazy<T>simple。这意味着不遵循以下做法:

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

考虑到Reactive Extensions的工作方式,您的reliable构造函数似乎没有执行任何I / O。在创建MyType时不会调用其SomeTransform方法。相反,可观察对象配置为在推入对象时调用MyType。这意味着从DI角度来看,您的注射仍然“简单”且快速。有时您的类需要在存储传入依赖项之上进行一些初始化。例如,创建并存储SomeTransform就是一个很好的例子。它允许延迟执行某些I / O,同时仍具有比仅“接收依赖项”更多的代码。]

但是您仍然在构造函数内部访问一个依赖项,如果该依赖项或其依赖项未完全初始化,可能会造成麻烦。此外,使用Reactive Extensions,您可以将运行时依赖性从Lazy<T>转换回IService(您已经具有从MyTypeMyType的设计时依赖性)。这与在.NET中处理事件非常相似。其结果是,即使预期IService的寿命较短,它也可能导致MyTypeIService保持活动。

因此,严格来说,从DI角度来看,此配置可能很麻烦。但是,很难想象使用Reactive Extensions时会有不同的模型。这意味着您必须将可观察对象的此配置移出构造函数,并在构造对象图之后执行此操作。但这可能会导致不得不打开您的类,以便MyType可以访问需要调用的方法。这也会导致Composition Root

换句话说,在使用Reactive Extensions时,最好有一些设计规则来防止麻烦。这些规则可以是:

  • 所有公开的Temporal Coupling属性应始终在其类型构造后始终完全初始化并可用。
  • 所有观察者和可观察对象的寿命应相同。
© www.soinside.com 2019 - 2024. All rights reserved.