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

发布于 2024-12-15 14:00:51 字数 1067 浏览 0 评论 0原文

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

假设我有一个对象(服务),它依赖于在 Ioc 中注册的 DataContext。

但它也需要一个 name 属性,我可以这样设计对象:

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

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

问题是与 Ioc 容器一起使用变得非常复杂,因为像 name 这样的字符串对象不容易注册,并且与 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 容器技术保持相当不可知的态度。

我知道这很大程度上取决于你想如何使用它,如果名称确实是可选的,选项 2 将是完美的。但在需要名称的情况下,您可以在代码中的另一点添加验证步骤,而是进行设计以使 Ioc 更简单。

想法?

I am looking for suggestions as to the best way to design objects for IoC

Suppose I have an object (Service) that has a dependency to a DataContext which is registered with Ioc.

But it also requires a name property, i could design the object like this:

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

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

The problem is it becomes very complicated to use with Ioc containers as a string object such as name is not easy to register and the usage becomes complicated with the Ioc container:
So resolution becomes confusing:

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

Another approach is to design it as follows:

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

    public string Name { get; set; }
} 

The resolution is now easier:

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

The only downsite is specifying the name is no longer required.
I would like to hear from DI or IoC experts how they would go about designing this and still stay fairly agnostic to the concrete Ioc container technology.

I know that a lot depends on how you want to use this, option 2 would be perfect if name really was optional. But in the case where name is required you could add the validation step at another point in code, but rather go for the design to make Ioc simpler.

Thoughts?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

弄潮 2024-12-22 14:00:51

您对 DI 容器的选择不应决定 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());

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

Your choice of DI Container shouldn't dictate the design of your API. If name isn't optional, it should be part of the constructor's signature (thus making it mandatory).

The next question then becomes how to configure the container without incurring tons of overhead. How to do that depends on the container. Here's how to implement a convention around a string argument in 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
    }
}

Then register the NameConvention with the container like this:

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

If your container doesn't have the appropriate extensibility points, pick a container that does.

萌梦深 2024-12-22 14:00:51

问题是使用 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" } }));

显然这会很快变得丑陋,所以您可能需要启动一些工厂/帮助器方法,如果您发现自己经常这样做。

The problem is it becomes very complicated to use with Ioc containers
as a string object such as name is not easy to register and the usage
becomes complicated with the Ioc container

Most good IoC containers will provide easy ways to supply constructor arguments when you do your configuration.

Your first example—constructor injection—is usually considered the preferred way. Think of your constructor as a contract to be followed, which, once satisfied, renders a valid object.

Your second code sample—property injection—is usually considered less preferable to constructor injection. Either way though, IoC containers will usually give you the ability to provide values for constructor parameters or properties at configuration, which will be supplied each time you ask your IoC to create you that object.

I'm not sure which IoC container you're looking to use, but here's a sample of code used to configure StructureMap, and provide string values for various services. Unless I'm misreading your question, this seems to be what you're looking to do.

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);
});

EDIT

Answering the comment, if you wanted to actually supply a constructor argument manually, it would look like this:

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

Obviously this can get ugly fast, so you may want to whip up some factory/helper methods if you find yourself doing this often.

完美的未来在梦里 2024-12-22 14:00:51

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

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

The approach I usually do in such situation is injecting settings object instead of string and then ask in constructor for property representing that string. Or in some cases even better whenever I need that string property I took it out of that settings, so that it can be changed (useful if it's really a program setting).

The other option is to use something like binding annotation. I don't know which dependency injection framework you are using, but here is how it can be done in guice (java) framework, which I am currently working with.

ゃ懵逼小萝莉 2024-12-22 14:00:51

如果您使用 Castle Windsor,则可以使用类型化工厂,您可以阅读有关 此处。本质上,类型化工厂允许您创建如下所示的界面。

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

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

If you are using Castle Windsor you can use typed factories, which you can read about here. Essentially, typed factories allow you to create an interface that looks like this.

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

Injecting this and calling Create() with the name of your choice Windsor will return a constructed IService implementation.

寂寞陪衬 2024-12-22 14:00:51

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

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

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

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

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

Design it like you always would, keeping good engineering practices in mind (SOLID etc). Then if your container of choice constraints you, you're either not using it properly, or you're using wrong container.

In Windsor's case you can easily provide inline, hardcoded dependencies to components at registration time:

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

You can also provide more dynamic dependencies or depend on values from your XML config if you need to change them post-compilation.

If the value is optional then make it optional, either by using constructor that specifies a default value for it, by using constructor overloads or as a property. Again, good container will handle all of those cases, if the one you're currently using doesn't perhaps switch to a better one.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文