我正在寻找有关为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更简单。
思想?
答案 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配置中的值。
如果值是可选的,则通过使用为其指定默认值的构造函数,使用构造函数重载或作为属性使其成为可选。同样,好的容器将处理所有这些情况,如果您当前使用的那个也不会切换到更好的情况。