相当确定,对于使用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的同时很好地克服这些限制?以下是要求摘要:
SomeTransform
),应该不运行_myObservable
应该是readonly
MyObservable
应该表现出Replay(1)
语义]StartWith
)Subscribe
内放入MyType
并缓存值(我们喜欢不变性)我尝试创建一个以false
开头的附加可观察对象,然后在初始化时将其设置为true
,然后将其与_myObservable
合并在一起,但并不能完全起作用。此外,它似乎不是最佳解决方案。本质上,我只想延迟Initialize()
完成。必须有某种我看不到的方法?
想到的一个简单解决方案是使用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有多个重载,如果您可能遇到多线程问题,则可以使用。
注入构造函数应为Lazy<T>
和simple。这意味着不遵循以下做法:
考虑到Reactive Extensions的工作方式,您的reliable构造函数似乎没有执行任何I / O。在创建MyType
时不会调用其SomeTransform
方法。相反,可观察对象配置为在推入对象时调用MyType
。这意味着从DI角度来看,您的注射仍然“简单”且快速。有时您的类需要在存储传入依赖项之上进行一些初始化。例如,创建并存储SomeTransform
就是一个很好的例子。它允许延迟执行某些I / O,同时仍具有比仅“接收依赖项”更多的代码。]
但是您仍然在构造函数内部访问一个依赖项,如果该依赖项或其依赖项未完全初始化,可能会造成麻烦。此外,使用Reactive Extensions,您可以将运行时依赖性从Lazy<T>
转换回IService
(您已经具有从MyType
到MyType
的设计时依赖性)。这与在.NET中处理事件非常相似。其结果是,即使预期IService
的寿命较短,它也可能导致MyType
被IService
保持活动。
因此,严格来说,从DI角度来看,此配置可能很麻烦。但是,很难想象使用Reactive Extensions时会有不同的模型。这意味着您必须将可观察对象的此配置移出构造函数,并在构造对象图之后执行此操作。但这可能会导致不得不打开您的类,以便MyType
可以访问需要调用的方法。这也会导致Composition Root。
换句话说,在使用Reactive Extensions时,最好有一些设计规则来防止麻烦。这些规则可以是: