Ninject / DI在简单场景中有用吗?

时间:2012-12-20 07:23:35

标签: dependencies inversion-of-control ninject code-injection

在过去的几天里,我观看了很多IOC / DI / ninject教程和视频,但我仍然不相信我明白了这一点。

在大多数例子中,他们通常都会说,如果我们想要一把Sword或者Shuriken会发生什么,我们需要定义IWeapon。我们想要从战士那里分离出实际武器的知识。

所以我们将所需的IWeapon注入战士然后让Ninject(或其他)让我们进入IWeapon所需的类(比如剑或手里剑),然后他们继续创建一个默认绑定创建一个剑的单一绑定到IWeapon。

我们如何告诉它使用哪一个?我们不能使用命名绑定,因为你不能在武器上设置命名绑定,然后是Get。

在我的情况下,我有一条消息,我正在从队列中读取,它将包含有关发送内容和发送给谁的所需详细信息。

我还有一个界面知道如何发送带有SMS,电子邮件,iphone等实现的消息。在这种情况下我无法理解如何使用DI而无需在我的代码中的某处放置一个开关: - (

public interface INotify
{
    void Send(Message msg);
}

public class Message
{
    public Message(INotify notify)
    {
        _notify = notify;
    }
    public void Send()
    {
        _notify.Send(this);
    }

    readonly INotify _notify;
    public string Type { get; set; }
    public string Text{ get; set; }
    public string Name { get; set; }
    public string Number { get; set; }
    public string Email { get; set; }
}


_kernel.Bind<INotify>().To<NotifyEmail>();
//kernel.Bind<INotify>().To<NotifySMS>().Named("sms");
//kernel.Bind<INotify>().To<NotifyAPNS>().Named("iphone");

var msg = _kernel.Get<Message>();
msg.Send();

是不是要让实例化所需的类变得容易?

4 个答案:

答案 0 :(得分:4)

DI的主要优点是可维护性。通过让DI为您注入依赖项(在代码中的统一位置配置),随着程序的发展,更容易交换进出不同的类。因为这些依赖关系通常基于接口,所以这会强制松散耦合。正如你提到的那些武器,我们的想法是你的不同组件不必“了解”彼此正常运行。

DI的最大优势在于测试。因为DI通常意味着通过接口而不是显式类来定义依赖关系,所以当您尝试测试程序的特定方面时,这可以很容易地存根这些依赖关系。

如果您的程序通过代理类访问Web服务,那么这将是有用的一个很好的示例,在您的应用程序测试期间调用可能是不合理的。您可以使用DI使其通过MyProxy访问WS,而不是通过IMyProxy访问WS。然后,您可以创建一个存根代理,该代理返回虚拟值,并允许您测试应用程序中的其他组件,而无需直接访问Web服务本身。

所有这一切,根据我的个人经验,DI并不是每个场景的灵丹妙药。 DI增加了一层复杂性以换取一层抽象。这对于应用程序架构的健壮性有很大好处,但也可能没有必要。关于SO What is dependency injection?的这篇文章对DI的有用性进行了一些讨论。这个答案https://stackoverflow.com/a/140655/1068266我认为在相当合理的情况下总结了DI。

简而言之,我不相信为DI而实施DI。阅读我在上面提到的帖子中引用的这篇文章http://www.jamesshore.com/Blog/Dependency-Injection-Demystified.html。这是我发现的最清晰,最简单的主题定义。如果你不认为你可以从基于该文章中解释的模式中受益,那么DI可能会在你的应用程序中构成一个不必要的复杂层。

答案 1 :(得分:4)

DI是关于将您的软件放在一起而不是解决您的业务逻辑。在您的场景中,您尝试从IoC容器中解析DTO,这被认为是不好的做法。

这意味着您必须以不同的方式为应用程序建模。例如。以下伪代码将为您提供一种处理此方案的方法:

public interface INotify
{
    string Type { get; }
    void Send(Message msg);
}

public class Message
{
    public string Type { get; set; }
    public string Text{ get; set; }
    public string Name { get; set; }
    public string Number { get; set; }
    public string Email { get; set; }
}

public class MessageProcessor
{
    public MessageProcessor(IEnumerable<INotify> notifiers, IMessageQueue) 
    {
        this.notifiers = notifiers.ToDictionary(n => n.Type, n);  
    }

    public void Process()
    {
        while (!TerminateApplication) 
        {
            var msg = this.queue.GetNextMessage();
            this.notifiers[msg.Type].Send(msg);
        }
    }
}


public void Main() 
{
    using (var kernel = new StandardKernel())
    {
        kernel.Bind<INotifier>().To<NotifyEmail>();
        kernel.Bind<INotifier>().To<NotifySms>();
        kernel.Bind<INotifier>().To<Notify>();
        kernel.Bind<MessageProcessor>().ToSelf();

        kernel.Get<MessageProcessor>().Process();
    }
}    

答案 2 :(得分:3)

依赖注入通常是为了解决必须为自己解决依赖关系的类,而是让它们依赖一些更高级别的代码来为它们完成这项工作。

依赖注入的好处是提高了可维护性,通过单一责任实现了简单性,并且是单元测试的重要推动因素,因为在测试时可以注入模拟依赖项。

像Ninject和其他类似IOC容器这样的框架通常提供了一种方便的方法来管理在应用程序级别存在的依赖项,其中特定的解决方案应用于依赖项的每个实例。

另一方面,您的消息示例正在处理场景驱动的情况,其中每个分辨率都取决于某些条件。

在场景驱动的情况下使用依赖注入仍然很有用,并且如果依赖注入,仍将为您提供所有好处。但正如您所说,有必要使用switch或if / else结构来提供正确的分辨率。这些条件结构应该驻留在一些更高级别的控制代码中,这些代码可以编排具有依赖关系的类

答案 3 :(得分:2)

您可以使用Contextual Binding,而不是使用命名绑定。重写您的配置以自动检测注射的确切位置取决于注射目标:

kernel.Bind<INotify>().To<NotifyEmail>();
kernel.Bind<INotify>().To<NotifySms>().WhenInjectedInto<SmsService>();
kernel.Bind<INotify>().To<NotifyAPNS>().WhenInjectedInto<IPhoneService>();

但据我所知,这将部分解决您的问题。

在我看来,你需要像 factory 而不是依赖注入来处理你的消息队列。例如:

var factory = new Dictionary<string, Func<Message>>
{
    { "unknow", () => new Message(new NotifyEmail()) },
    { "sms", () => new Message(new NotifySms()) },
    { "iphone", () => new Message(new NotifyAPNS()) }
};

factory["iphone"]().Send();

如果你必须构建INotify的复杂实现,你可以使用ninject.extensions.factory同时获得依赖注入抽象工厂的好处。在这种情况下,您可以定义新的工厂接口并定义其工作方式:

kernel.Bind<INotify>().To<NotifyEmail>()
        .NamedLikeFactoryMethod<INotify, INotifocationFactory>(f => f.GetNotifyEmail());
kernel.Bind<INotify>().To<NotifySms>()
        .NamedLikeFactoryMethod<INotify, INotifocationFactory>(f => f.GetNotifyEmail());
kernel.Bind<INotify>().To<NotifyAPNS>()
        .NamedLikeFactoryMethod<INotify, INotifocationFactory>(f => f.GetNotifyEmail());

// receive INotifocationFactory using constructor injection,
// do not resolve it directly, because this will led you to ServiceLocator anti-pattern
var abstractFactory = kernel.Get<INotifocationFactory>();

var factory = new Dictionary<string, Func<Message>>
{
    { "unknow", () => new Message(abstractFactory.GetNotifyEmail()) },
    { "sms", () => new Message(abstractFactory.GetNotifyEmail()) },
    { "iphone", () => new Message(abstractFactory.GetNotifyAPNS()) }
};

factory["iphone"]().Send();

kernel.Bind<INotifocationFactory>().ToFactory();

最后一个样本非常复杂,对于一个5美分概念来说可能是 25美元的期限。所以,在特定情况下使用什么解决方案

都取决于你