Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/google-apps-script/6.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 注入依赖项并将其一个成员设置为“this”,这是一种代码味道吗?_C#_Testing_Dependency Injection - Fatal编程技术网

C# 注入依赖项并将其一个成员设置为“this”,这是一种代码味道吗?

C# 注入依赖项并将其一个成员设置为“this”,这是一种代码味道吗?,c#,testing,dependency-injection,C#,Testing,Dependency Injection,注入依赖项并将其属性之一设置为当前实例是否是一种代码味道?我以这种方式设置代码,以便完全隔离服务实现。我有一系列测试都通过了(包括在逻辑类中设置StreamingSubscriber实例) 比如说 public class StreamingSubscriber { private readonly ILogic _logic; public StreamingSubscriber(ILogic logic) { _logic =

注入依赖项并将其属性之一设置为当前实例是否是一种代码味道?我以这种方式设置代码,以便完全隔离服务实现。我有一系列测试都通过了(包括在逻辑类中设置
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
    }
}
如果这是一个代码气味,你如何着手修复它

编辑 我选择此实现的原因是,我希望能够在调用
ExchangeLogic
中的
streamingConnection
时测试它是否会使用电子邮件。当前的设计虽然不完美,但允许我编写类似的测试

    [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());
    }
[测试]
public void FiringOnNotificationEvent_willconsumeremail()
{
//安排
var subscriber=新的StreamingSubscriber(ConsumerMock.Object、ExchangeLogicMock.Object);
//表演
subscriber.OnNotificationEvent(It.IsAny(),It.IsAny());
//断言
ConsumerMock.Verify(x=>x.Consume(It.IsAny()),Times.Once());
}

现在,如果我告诉我的
ExchangeLogic
使用电子邮件,如果不进行全面的集成测试,这显然是不可能实现的。

这本身并不是一种代码味道,不是


然而,通过setter进行这项工作会造成一种情况,您可能会遇到计时问题——如果有人调用subscribe,而StreamingSubscriber尚未设置,该怎么办?现在您必须编写代码来防止这种情况。我会避免使用setter并重新排列它,这样您就可以调用“\u logic.Subscribe(this)”。

是的,这很糟糕;您正在创建一个循环依赖项

通常,不使用构造函数注入可以被认为是一种代码味道,部分原因是当构造函数是唯一的注入点时,依赖项注入容器不可能满足循环依赖关系图。通过这种方式,构造函数注入可以防止您创建这样的情况

在这里,您使用属性注入使循环依赖成为可能,但是针对这种代码气味的规定修复方法是重新设计您的系统,以避免循环依赖的需要


NET中的依赖项注入一书在第6章:DI重构,第6.3节:解析循环依赖项中讨论了这一点。

我不认为这个特定场景太难闻。在组件和它的依赖项之间有一个循环引用是完全合法的。你可以通过引进一家工厂使它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
,您可以使用这样一种简单的匿名方法(如果您愿意,您也可以从签名中删除
发件人
,我发现有一半时间我不需要它):


仅供参考,并非所有代码气味都需要“修复”。这就是为什么它是“代码气味”而不是“bug”。使用ctor注入创建循环依赖项是绝对可能的。他在本例中使用属性注入有一个明确的原因,那就是因为他处理的是接口(ILogic),而你不能用接口构造,也不要约束所有实现它的类,让构造函数使用
StreamingSubscriber
arg.@Mark H:对不起,但是什么
ILogic
应更改为在其构造函数中使用一个
StreamingSubscriber
arg,或者如果该参数不可用,则应定义一个新接口,继承执行此操作的
ILogic
。@Domenic:如果可能的话,这将是一个改进。接口没有构造函数。我不认为循环依赖总是自动坏的。您可以在类之间建立双向关系,其中每个类都需要对另一个类的引用。必须首先构造一个对象,然后另一个对象必须以某种方式获得对该对象的引用,这不能仅通过构造函数注入来完成。
streamingConnection.OnNotificationEvent += (sender, args) => _StreamingSubscriber.OnNotificationEvent(sender, args);