有没有像Unity一样的东西,适用于不需要界面的简单事物?

4
也许我错用了Unity,但是我还是尝试了一下。我有几个应用程序,它们都加载相同的插件程序集。所有程序集都需要一个库,我希望它们能够通过Unity访问这个库。然而,为了使用Unity或任何其他IoC框架,我需要为该库编写一个接口。我可能会这样做,但由于接口除了支持Unity外没有其他用途,我担心这意味着我要么没有理解其意义,要么误用了该框架。如果我避免使用提供DI的东西,那么我就必须将库类变成单例,然后将其传递给所有插件构造函数,或通过公共属性传递。但我不想这么做。
话虽如此,但在实际使用Unity之前,还有一个问题 – 虽然Unity会让我通过Resolve<>请求库,但我的插件仍然需要引用在主应用程序中创建的Unity实例。那么这种情况下您唯一的选择是将Unity引用传递给所有插件吗?但从那时起,您可以方便地使用Unity来获取所有其他依赖项,对吗?
更新
我意识到自己错了,但希望有人能为我澄清一下——我不应该到处传递对Unity的引用!我只需在我的应用程序中创建容器,然后注册所有类型。然后当我实例化所有插件时,它们应该可以神奇地使用这些已注册的接口,几乎不需要额外的工作,对吗?在我的情况下,我的构造函数必须是无参数的,因为我的插件加载器无法处理参数,在这种情况下,我将不得不使用属性注入来让它们访问接口,对吗?
另一个更新
我继续尝试Unity。我注册了所有插件都需要的类的实例。我也知道我最终会遇到一个问题,因为它们是无参的(我可能需要传递一个对Unity的引用来使它们运行)。但是,现在我直接创建插件,并通过Resolve方法完成。因此,代码基本上如下所示:
// app code
ICandySettings _candy_settings = new CandySettings();
IUnityContainer unity = new UnityContainer().RegisterInstance<ICandySettings>( _candy_settings);
CandyPlugin _plugin = unity.Resolve<Candy>(); // throws null reference exception, see below.

// plugin code
public class Candy
{
  [Dependency]
  ICandySettings CandySettings { get; set; }

  ...

  public Candy()
  {
    CandySettings.GetSetting("box"); // CandySettings is null!  why?  Didn't Unity do this for me?
  }
}

目前我的问题是,我认为(鉴于我的有限知识),Unity应该会自动将插件的CandySettings引用设置为通过RegisterInstance注册的任何实例,但事实并非如此。

一个可行的选项

如果我跳过烟雾和镜子的东西,只需在插件的构造函数中传递我的UnityContainer,然后我就可以调用Unity.Resolve()来设置我的CandySettings属性的值,一切都能正常工作。我很想知道为什么[Dependency]属性没有做我认为它应该做的事情。如果我没弄错的话,我实际上不需要在我的插件加载器的每个构造函数中都传递Unity。如果[Dependency]正在起作用,我只需要使用Unity.Resolve(),那么它可能会起作用。但是,现在我明白了大家所说的选择IoC容器会强制整个开发团队使用它的原因。

MEF!

到目前为止,MEF对我来说获胜了。它非常简单,并且神奇的烟雾和镜子的东西非常适合我的需求(目前)。但我仍然想让Unity工作。我觉得奇怪的是,对于MEF,我只需要组成部分,其他一切都会自然而然地实现,而我似乎无法让Unity自动注入所有东西,我必须通过引用到每个地方传递的Unity来解决所有问题。这不对。

更多MEF

我喜欢使用MEF非常容易解析多个对象,但是在使用策略模式指定代码行为的情况下怎么办?目前,只需将引用从一种行为的实现更改为另一种行为的实现就可以轻松完成,它就可以正常工作。有人使用MEF做吗?正确的方法是使用ImportMany,然后使用额外的代码确定应调用列表中的哪个行为吗?


1
我认为“面向接口编程,而不是实现”这一设计原则在许多情况下都被证明可以增加价值。我不会说你只是为了Unity而实现接口。 - Marvin Smit
我完全同意,但就这种情况而言,我并不认为接口真的对我有任何好处。然而,我本来要说“我们绝不会从数据库更改后端”,但你知道怎么回事… ;) - Dave
@Marvin:这是真的,但在许多情况下也已经显示出增加了复杂性。实际上,.NET框架类库设计指南建议在适当的情况下优先使用抽象类而不是接口,因为它们更易于理解和发现。 - Reed Copsey
@Reed,所以一个五年前的书中的评论,它在两年前的第二版中没有被更新的评论,比最近为.Net 4发布刷新的MSDN上提出的最新指南更加新吗?另外,如果您有一个抽象基类而不是一个接口,您如何进行依赖注入、控制反转或模拟? - Rodney S. Foley
@Creepy: IoC 在大多数框架中使用基类都很好用。(包括现在直接成为 .NET 4 一部分的 MEF)... 测试和模拟在基类上也非常出色。 - Reed Copsey
显示剩余2条评论
4个回答

2

大多数IoC容器可以映射具体类和接口,只是使用接口被认为是更好的实践,因为它涉及设计和测试。

如果你只想允许提供一些常见类型,你可能想要使用System.IServiceProvider来自己创建。它是内置的,所以你不需要将你的IoC依赖级联到你的消费代码中。

public class SomePlugin
{
 public SomePlugin(IServiceProvider serviceProvider)
 {
  _foo = serviceProvider.GetService(typeof(IFoo)) as IFoo;
 }
}

或者你可以像在Common Service Locator库中一样拥有一个单例服务定位器

public class SomePlugin
{
 public SomePlugin()
 {
  _foo = ServiceLocator.Current.GetService(typeof(IFoo)) as IFoo;
 }
}

编辑:考虑到您的更新,我建议在实例化插件后使用BuildUp来注入属性依赖项。请参阅此文章

PluginInstance plugin = // already loaded from whatever you're already doing
container.BuildUp<PluginInstance>(plugin);

System.IServiceProvider可能是我见过最缺乏文档说明的接口之一。你能推荐一些好的网站来描述如何正确实现IServiceProvider.GetInterface()吗? - Dave
嗯...难道真的像我的类继承自IServiceProvider一样简单,然后在GetService()中检查传入的Type并将其与我想要返回的对象的Type进行比较,如果匹配,就直接返回它吗?我现在会尝试这样做,看看会发生什么。但是派生的IServiceProvider如何在ServiceLocator中注册呢? - Dave
就类型检查而言,是的。您可以使其尽可能简单或高级。至于访问派生的IServiceProvider,我认为您可以在几个不同的选项之间进行选择:
  1. 修改插件构造函数以使用一个IServiceProvider实例。这可能是最简单的解决方案,因为插件不需要任何额外的依赖项。 2)创建一个带有您的IServiceProvider静态实例的类,在插件使用它之前进行设置。您的插件将需要引用具有静态实例类的库。
- ray2k
  1. 使用插件类的读写属性,该属性的类型为IServiceProvider,在与插件实例交互之前设置。可能需要更新比您想要的更多的插件。
  2. 使用CommonServiceLocator库并编写自己的适配器。CSL具有类似于IServiceProvider的自己的接口,您可以实现它并将其传输到静态ServiceLocator.Current实例中。插件将获得对CSL的依赖。
如果您的东西像您所说的那样简单,我会选择1)并结束一天的工作。
- ray2k
在这种情况下,使用Unity的RegisterInstance似乎更容易使一切正常运行。您能否对我原来问题的更新发表评论吗?谢谢! - Dave
显示剩余2条评论

2
如果您的依赖注入需求较小(从听起来的情况来看是这样),您可能想考虑尝试一下MEF。它很轻量级且易于使用,并且有一个优点,就是直接在.NET 4框架中,这意味着如果您将来转移到.NET 4,就不需要额外的部署要求。
在此期间,它在Codeplex网站上也支持3.5。
MEF之所以好用,是因为它可以处理任何类型,而不仅仅是接口。
话虽如此,如果您希望插件使用应用程序提供的“库”,您很可能总是需要该库可用并被插件引用(或者至少为API提供一组基类或接口)。

我现在会去检查它。谢谢你的建议,Reed。 - Dave
Reed,这听起来是一种不错的方式来为我的应用程序编写插件。目前,我正在使用接口来定义不同类型的插件,然后我的插件加载器会查看程序集信息以确定每个插件支持哪些接口。MEF似乎也是这样做的,但可能更加清晰明了。我理解得对吗? - Dave
@Dave:是的。它是专门为扩展应用程序(即插件)而设计的,尽管它作为一种通用的IoC选项也很有用。不过,MEF对于插件的创建和组合真的非常好。 - Reed Copsey
@Dave:我不使用Unity(对我来说有点过重,比MEF更难),但是从我记得的来看,这里的主要问题是几乎所有IoC容器都存在的问题。除非您使用构造函数注入(即:直接将参数传递到构造函数中 - MEF支持此功能,不确定Unity是否支持),否则容器需要先构造您的对象,然后才能设置所有依赖项。在您的情况下,这意味着Unity将创建您的对象(并运行您的构造函数),获取对象后,然后适当地设置CandySettings。 - Reed Copsey
啊,我明白了!那确实有些道理。也许我可以推迟使用CandySettings库,但我现在还不知道。我已经分支了我的代码,现在要尝试一下MEF。感谢您的帮助! - Dave
显示剩余4条评论

0

Unity在发布的示例中无法正常工作的原因是

[Dependency]
ICandySettings CandySettings { get; set; }

默认的可访问性是私有的。Unity要求属性为公共的(这样它才能调用setter)。

在最新版本的Unity中(错误消息/行为可能与旧版本不同),使用您发布的代码,我会收到一个异常:“类型ConsoleApplication32.Candy上的属性CandySettings不可设置。”

然而,现在我明白了每个人都在说什么,选择一个IoC容器将强制整个开发团队使用它。

是和否。显然,类需要被连接起来,以便正确注入依赖项(希望在组合根中)。但是,应用程序代码不应该引用容器(例如使用DependencyAttribute或传递IUnityContainer)。在发布的示例中,可以通过为Candy类注册一个InjectionProperty来实现:

ICandySettings _candy_settings = new CandySettings();
IUnityContainer unity = new UnityContainer().RegisterInstance<ICandySettings>( _candy_settings);
unity.RegisterType<Candy>(new InjectionProperty("CandySettings"));
CandyPlugin _plugin = unity.Resolve<Candy>(); 

我(和许多其他人)也更喜欢在构造函数中注入依赖项(构造函数注入),这样Unity就会在构造函数中自动注入依赖项:

public class Candy
{
    public ICandySettings CandySettings { get; private set; }

    ...

    public Candy(ICandySettings candySettings)
    {
        this.CandySettings = candySettings;
        this.CandySettings.GetSetting("box"); 
    }
}

注册码与原问题中的相同:

ICandySettings _candy_settings = new CandySettings();
IUnityContainer unity = new UnityContainer().RegisterInstance<ICandySettings>(_candy_settings);

var _plugin = unity.Resolve<Candy>(); // Everything is OK :)

虽然回答有些晚了,但我想解释一下发生了什么以及如何让它正常工作。 - Randy Levy

0
首先,我有一个单例,它持有对我的Unity容器实例的引用,因此我不必在各处传递它。
其次,您可以自由使用unityContainer.RegisterInstance(myInstance);unityContainer.RegisterType<MyClass,MyClass>();

是的,我在我的情况下使用RegisterInstance。我正在重新观看微软的Unity视频,看看我第一次错过了什么。我相信一旦我使其正常工作,我会因为花费了这么长时间才开始打自己。 - Dave

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接