我正在使用Simple Injector,但也许我需要的更多是概念性答案。
这是交易,假设我的应用程序设置有一个界面:
public interface IApplicationSettings
{
bool EnableLogging { get; }
bool CopyLocal { get; }
string ServerName { get; }
}
然后,通常会有一个实现IApplicationSettings的类,从指定的源获取每个字段,例如:
public class AppConfigSettings : IApplicationSettings
{
private bool? enableLogging;
public bool EnableLogging
{
get
{
if (enableLogging == null)
{
enableLogging = Convert.ToBoolean(ConfigurationManager.AppSettings["EnableLogging"];
}
return enableLogging;
}
}
...
}
HOWEVER!假设我想从app.config获取EnableLogging
,从数据库获取CopyLocal
,从另一个获取当前计算机的实现获取ServerName
名称。我希望能够混合匹配我的应用程序配置,而无需创建9个实现,每个组合一个。
我假设我无法传递任何参数,因为接口由注入器(容器)解析。
我最初想到了这个:
public interface IApplicationSettings<TEnableLogging,TCopyLocal,TServerName>
where TEnableLogging : IGetValue<bool>
where TCopyLocal : IGetValue<bool>
where TServerName : IGetValue<string>
{
TEnableLogging EnableLog{get;}
TCopyLocal CopyLocal{get;}
TServerName ServerName{get;}
}
public class ApplicationSettings<TEnableLogging,TCopyLocal,TServerName>
{
private bool? enableLogging;
public bool EnableLogging
{
get
{
if (enableLogging == null)
{
enableLogging = Container.GetInstance<TEnableLogging>().Value
}
return enableLogging;
}
}
}
然而,有了这个,我有一个主要问题:我如何知道如何创建TEnableLogging
的实例(这是IGetValue<bool>
)?哦,假设IGetValue<bool>
是一个具有Value属性的接口,它将由具体类实现。但具体类可能需要一些细节(比如app.config中键的名称)或不需要(我可能只想返回总是如此)。
我对依赖注入相对较新,所以也许我的想法是错误的。有没有人对如何做到这一点有任何想法?
(你可以使用另一个DI库回答,我不介意。我想我只需要抓住它的概念。)
答案 0 :(得分:15)
你肯定会走错路。
几年前,我构建了一个包含界面的应用程序,就像你的IApplicationSettings
一样。我相信我将其命名为IApplicationConfiguration
,但它也包含所有应用程序的配置值。
虽然它帮助我首先使我的应用程序可测试,但经过一段时间后,设计开始受阻。许多实现依赖于该接口,但它不断变化,并随之改变实现和测试版本。
就像你我实施了一些延迟加载,但这有一个可怕的不利方面。当其中一个配置值丢失时,我只发现它是在第一次调用该值时执行的。这导致配置难以验证。
我花了几次重构来找出问题的核心是什么。大接口是一个问题。我的IApplicationConfiguration
课程违反了Interface Segregation Principle,结果是可维护性差。
最后我发现这个界面完全无用。除了违反ISP之外,这些配置值描述了一个实现细节,而不是进行应用程序范围的抽象,更好的是直接为每个实现提供他们需要的配置值,并且只提供他们需要的值。
执行此操作时,最简单的方法是将这些值包装到参数对象中(即使它只是一个值),并将这些配置值注入构造函数中。这是一个例子:
var enableLogging =
Convert.ToBoolean(ConfigurationManager.AppSettings["EnableLogging"]);
container.RegisterSingleton(new LoggerSettings(loggingEnabled: enableLogging));
在这种情况下,LoggerSettings
是特定于Logger
的配置对象,需要将其作为构造函数参数。
执行此操作时,enableLogging
值仅从配置文件中读取一次,并在应用程序启动期间完成。这使得它很快并且在缺少值时在应用程序启动时失败。