IoC具有值类型和对象类型依赖性

时间:2011-11-14 16:23:27

标签: c# dependency-injection inversion-of-control constructor-injection property-injection

我正在寻找有关为IoC设计对象的最佳方法的建议

假设我有一个对象(Service)依赖于向Ioc注册的DataContext。

但它还需要一个名称属性,我可以像这样设计对象:

class Service
{
    public Service(IDataContext dataContext, 
        string name)
    {
        this._dataContext = dataContext;
        this._name = name
    }

    public string Name
    {
        get
        {
            return _name;
        }
    }
}

问题是使用Ioc容器变得非常复杂,因为名称等字符串对象不容易注册,并且Ioc容器的使用变得复杂: 因此解决方案变得令人困惑:

var service = Ioc.Resolve<Service>( ?? )

另一种方法是按如下方式设计:

class Service
{
   public Service(IDataContext dataContext)
   {
        this._dataContext = dataContext;
   }

    public string Name { get; set; }
} 

分辨率现在更容易:

var service = Ioc.Resolve<Service>();
service.Name = "Some name";

唯一的缺点是不再需要指定名称。 我想听听DI或IoC专家如何设计这个,并且仍然坚持使用具体的Ioc容器技术。

我知道很多取决于你想如何使用它,如果name真的是可选的,选项2将是完美的。但是在需要名称的情况下,你可以在代码的另一个点添加验证步骤,而是去设计使Ioc更简单。

思想?

5 个答案:

答案 0 :(得分:4)

您选择的DI Container不应该规定您的API设计。如果name不是可选的,它应该是构造函数签名的一部分(因此强制它)。

接下来的问题就是如何配置容器而不会产生大量开销。怎么做取决于容器。以下是如何在Castle Windsor中围绕字符串参数实现约定:

public class NameConvention : ISubDependencyResolver
{
    public bool CanResolve(CreationContext context,
        ISubDependencyResolver contextHandlerResolver,
        ComponentModel model, DependencyModel dependency)
    {
        return dependency.TargetType == typeof(string)
            && dependency.DependencyKey == "name";
    }

    public object Resolve(CreationContext context,
        ISubDependencyResolver contextHandlerResolver,
        ComponentModel model, DependencyModel dependency)
    {
        return "foo"; // use whatever value you'd like,
                      // or derive it from the provided models
    }
}

然后在容器中注册NameConvention,如下所示:

container.Kernel.Resolver.AddSubResolver(new NameConvention());

如果您的容器没有适当的扩展点,请选择一个容器。

答案 1 :(得分:3)

  

问题是与Ioc容器一起使用变得非常复杂   作为字符串对象,如名称不容易注册和使用   使用Ioc容器变得复杂

在进行配置时,大多数优秀的IoC容器都会提供简单的方法来提供构造函数参数。

您的第一个示例构造函数注入通常被认为是首选方式。将构造函数视为要遵循的契约,一旦满足,就会呈现有效对象。

您的第二个代码示例属性注入通常被认为不如构造函数注入。无论哪种方式,IoC容器通常都能让您在配置时为构造函数参数属性提供值,每当您要求IoC创建该对象时,都会提供这些值。

我不确定您要使用哪个IoC容器,但这里是用于配置StructureMap的代码示例,并为各种服务提供字符串值。除非我误解你的问题,否则这似乎就是你想要做的事情。

ObjectFactory.Initialize(x =>
{
    x.For<ICatalogAdminService>().Use<DLinkCatalogAdminService>()
        .Ctor<string>("catalogConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString)
        .Ctor<string>("contentConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString)
        .Ctor<string>("webCatalogConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_WebConnectionString"].ConnectionString)
        .Ctor<string>("dlinkPromotionAdminConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString);
    x.For<IContentManagementAdminService>().Use<DLinkContentManagementAdminService>()
        .Ctor<string>("contentConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString)
        .Ctor<string>("webCatalogConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_WebConnectionString"].ConnectionString)
        .Ctor<string>("dlinkPromotionConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString);
    x.For<IPromotionAdminService>().Use<DLinkPromotionAdminService>()
        .Ctor<string>("catalogConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString)
        .Ctor<string>("promotionConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString);
    x.For<ISearchService>().Use<Extractor>();
    x.For<IImporter>().Use<Importer>();
    x.For<IOrderAdminService>().Use<DLinkOrderAdminService>()
        .Ctor<string>("contentConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_AdminConnectionString"].ConnectionString)
        .Ctor<string>("orderConnectionString").Is(ConfigurationManager.ConnectionStrings["myCompany_OrdersConnectionString"].ConnectionString);
});

修改

回答评论,如果你想手动提供构造函数参数,它将如下所示:

ObjectFactory.GetInstance<ICatalogAdminService>(new ExplicitArguments(
    new Dictionary<string, object>() { { "parameter1", "someValue" } }));

显然这可能会变得很难看,所以如果你经常这样做,你可能想要制定一些工厂/助手方法。

答案 2 :(得分:2)

我在这种情况下通常采用的方法是注入设置对象而不是字符串,然后在构造函数中询问表示该字符串的属性。或者在某些情况下甚至更好,每当我需要该字符串属性时,我将其从该设置中取出,以便可以更改它(如果它真的是程序设置则很有用)。

另一种选择是使用绑定注释之类的东西。我不知道您正在使用哪个依赖注入框架,但这里是如何在guice(java)框架中完成的,我目前正在使用它。

答案 3 :(得分:1)

如果您使用Castle Windsor,您可以使用类型化工厂,您可以阅读here。从本质上讲,类型化工厂允许您创建一个看起来像这样的接口。

public interface IServiceFactory
{
    IService Create(string name);
}

注入此项并使用您选择的名称调用Create() Windsor将返回构造的IService实现。

答案 4 :(得分:1)

像往常一样设计它,牢记良好的工程实践(SOLID等)。然后,如果你选择的容器限制你,你要么没有正确使用它,要么你使用了错误的容器。

Windsor的情况下,您可以在注册时轻松地为组件提供内联的,硬编码的依赖项:

container.Register(Component.For<Foo>().DependsOn(new{name = "Stefan"});

如果需要在编译后更改它们,您还可以提供更多动态依赖项或依赖XML配置中的值。

如果值是可选的,则通过使用为其指定默认值的构造函数,使用构造函数重载或作为属性使其成为可选。同样,好的容器将处理所有这些情况,如果您当前使用的那个也不会切换到更好的情况。