注入依赖项并将其中一个属性设置为当前实例是代码味道吗?我以这种方式设置代码,因此我可以完全隔离服务实现。我有一系列测试全部通过(包括在逻辑类中设置StreamingSubscriber
实例)。
例如
public class StreamingSubscriber
{
private readonly ILogic _logic;
public StreamingSubscriber(ILogic logic)
{
_logic = logic;
// Not sure I like this...
_logic.StreamingSubscriber = this;
}
public void OnNotificationEvent(object sender, NotificationEventArgs args)
{
// Do something with _logic
var email = _logic.FetchEmail(args);
// consume the email (omitted for brevity)
}
}
public class ExchangeLogic : ILogic
{
public StreamingSubscriber StreamingSubscriber { get; set; }
public void Subscribe()
{
// Here is where I use StreamingSubscriber
streamingConnection.OnNotificationEvent += StreamingSubscriber.OnNotificationEvent;
}
public IEmail FetchEmail(NotificationEventArgs notificationEventArgs)
{
// Fetch email from Exchange
}
}
如果这是代码味道你怎么去解决它?
我选择此实现的原因是因为我希望能够在调用来自streamingConnection
的{{1}}时测试它是否会使用该电子邮件。目前的设计虽然不完美,但我可以编写类似这样的测试。
ExchangeLogic
现在,如果没有进行全面的集成测试,这显然是不可能的。如果我告诉我的 [Test]
public void FiringOnNotificationEvent_WillConsumeEmail()
{
// Arrange
var subscriber = new StreamingSubscriber(ConsumerMock.Object, ExchangeLogicMock.Object);
// Act
subscriber.OnNotificationEvent(It.IsAny<object>(), It.IsAny<NotificationEventArgs>());
// Assert
ConsumerMock.Verify(x => x.Consume(It.IsAny<IEmail>()), Times.Once());
}
使用电子邮件。
答案 0 :(得分:4)
它本身并没有打击我的代码味道,没有。
然而,通过setter进行这项工作会产生一种情况,你可能会遇到时间问题 - 如果有人调用了订阅并且还没有设置StreamingSubscriber呢?现在你必须编写代码来防范它。我会避免使用setter并重新排列它,所以你会调用“_logic.Subscribe(this)”。
答案 1 :(得分:2)
是的,这很糟糕;你正在创建循环依赖。
通常,不使用构造函数注入可以被认为是代码气味,部分原因是当构造函数是唯一的注入点时,依赖注入容器不可能满足循环依赖图。通过这种方式,构造函数注入可以防止您创建这样的情况。
在这里,你使用属性注入来实现循环依赖,但是对于这种代码气味的规定修复是重新设计你的系统以避免需要循环依赖。
.NET中的依赖注入一书在第6章:DI重构,第6.3节:解决循环依赖关系中讨论了这一点。
答案 2 :(得分:2)
我没有看到这种特殊情况太臭了。在组件及其依赖项之间使用循环引用是完全合法的情况。您可以通过引入工厂使其100%防弹,由您来判断这样做是否有任何好处。
public class StreamingSubscriber
{
private readonly ILogic _logic;
public StreamingSubscriber(ILogicFactory logicFactory)
{
_logic = logicFactory.Create(this);
}
public void OnNotificationEvent(object sender, NotificationEventArgs args)
{
// Do something with _logic
var email = _logic.FetchEmail(args);
// consume the email (omitted for brevity)
}
}
public class ExchangeLogic : ILogic
{
private readonly StreamingSubscriber _StreamingSubscriber;
public ExchangeLogic (StreamingSubscriber subscriber){
_StreamingSubscriber = streamingSubscriber;
Subscribe();
}
private void Subscribe()
{
// Here is where I use StreamingSubscriber
streamingConnection.OnNotificationEvent += _StreamingSubscriber.OnNotificationEvent;
}
public IEmail FetchEmail(NotificationEventArgs notificationEventArgs)
{
// Fetch email from Exchange
}
}
我确实发现你的逻辑实现直接将事件连接到它的依赖方法比整个循环引用问题更麻烦。我会将其隔离,以便StreamingConnection
中的更改不会影响StreamingSubscriber
,您可以使用简单的匿名方法执行此操作(如果您需要,也可以从签名中删除sender
,有一半我发现我不需要它):
streamingConnection.OnNotificationEvent += (sender, args) => _StreamingSubscriber.OnNotificationEvent(sender, args);