假设我正在为我的应用程序定义一个浏览器实现类:
class InternetExplorerBrowser : IBrowser {
private readonly string executablePath = @"C:\Program Files\...\...\ie.exe";
...code that uses executablePath
}
乍一看这似乎是个好主意,因为 executablePath
数据靠近将使用它的代码。
当我尝试在另一台具有外语操作系统的计算机上运行相同的应用程序时,问题就来了:executablePath
将具有不同的值。
我可以通过 AppSettings 单例类(或其等效类之一)解决此问题,但没有人知道我的类实际上依赖于此 AppSettings 类(这与 DI ideias 相悖)。这也可能给单元测试带来困难。
我可以通过构造函数传入 executablePath
来解决这两个问题:
class InternetExplorerBrowser : IBrowser {
private readonly string executablePath;
public InternetExplorerBrowser(string executablePath) {
this.executablePath = executablePath;
}
}
但这会在我的 Composition Root
(将完成所有需要的类连接的启动方法)中引发问题,因为该方法必须知道如何连接并且必须知道所有这些小设置数据:
class CompositionRoot {
public void Run() {
ClassA classA = new ClassA();
string ieSetting1 = "C:\asdapo\poka\poskdaposka.exe";
string ieSetting2 = "IE_SETTING_ABC";
string ieSetting3 = "lol.bmp";
ClassB classB = new ClassB(ieSetting1);
ClassC classC = new ClassC(B, ieSetting2, ieSetting3);
...
}
}
这很容易变成一团糟。
我可以通过传递表单的接口(interface)来解决这个问题
interface IAppSettings {
object GetData(string name);
}
所有需要某种设置的类。然后我可以将其实现为一个嵌入了所有设置的常规类,或者一个从 XML 文件中读取数据的类,类似的东西。如果这样做,我应该为整个系统提供一个通用的 AppSettings 类实例,还是为每个可能需要的类关联一个 AppSettings 类?这当然看起来有点矫枉过正。此外,将所有应用程序设置放在同一个位置可以很容易地查看和查看在尝试将程序移动到不同平台时可能需要做的所有更改。
处理这种常见情况的最佳方法是什么?
编辑:
如果使用一个所有设置都硬编码在其中的 IAppSettings
呢?
interface IAppSettings {
string IE_ExecutablePath { get; }
int IE_Version { get; }
...
}
这将允许编译时类型安全。如果我看到接口(interface)/具体类增长得太多,我可以创建 IMyClassXAppSettings
形式的其他更小的接口(interface)。在中/大型项目中是否会负担过重?
我还阅读了有关 AOP 及其处理横切关注点的优势(我想这就是其中之一)。它不能也提供解决这个问题的方法吗?也许像这样标记变量:
class InternetExplorerBrowser : IBrowser {
[AppSetting] string executablePath;
[AppSetting] int ieVersion;
...code that uses executablePath
}
然后,在编译项目时,我们还会有编译时安全性(让编译器检查我们实际实现的代码是否会编织数据。当然,这会将我们的 API 绑定(bind)到这个特定的方面。
最佳答案
各个类应尽可能不受基础结构的影响——像 IAppSettings
、IMyClassXAppSettings
和 [AppSetting]
这样的构造将组合细节渗入到最简单的类,实际上只依赖于原始值,例如 executablePath
。依赖注入(inject)的艺术在于关注点的分解。
我已经使用 Autofac 实现了这个确切的模式,它有类似于 Ninject 的模块并且应该产生类似的代码(我意识到这个问题没有提到 Ninject,但是 OP 在评论中提到了)。
模块按子系统组织应用程序。模块公开子系统的可配置元素:
public class BrowserModule : Module
{
private readonly string _executablePath;
public BrowserModule(string executablePath)
{
_executablePath = executablePath;
}
public override void Load(ContainerBuilder builder)
{
builder
.Register(c => new InternetExplorerBrowser(_executablePath))
.As<IBrowser>()
.InstancePerDependency();
}
}
这给组合根留下了同样的问题:它必须提供 executablePath
的值。为了避免配置汤,我们可以编写一个独立的模块来读取配置设置并将它们传递给 BrowserModule
:
public class ConfiguredBrowserModule : Module
{
public override void Load(ContainerBuilder builder)
{
var executablePath = ConfigurationManager.AppSettings["ExecutablePath"];
builder.RegisterModule(new BrowserModule(executablePath));
}
}
您可以考虑使用自定义配置部分而不是 AppSettings
;更改将本地化到模块:
public class BrowserSection : ConfigurationSection
{
[ConfigurationProperty("executablePath")]
public string ExecutablePath
{
get { return (string) this["executablePath"]; }
set { this["executablePath"] = value; }
}
}
public class ConfiguredBrowserModule : Module
{
public override void Load(ContainerBuilder builder)
{
var section = (BrowserSection) ConfigurationManager.GetSection("myApp.browser");
if(section == null)
{
section = new BrowserSection();
}
builder.RegisterModule(new BrowserModule(section.ExecutablePath));
}
}
这是一个很好的模式,因为每个子系统都有一个独立的配置,可以在一个地方读取。这里唯一的好处是意图更明显。但是,对于非 string
值或复杂架构,我们可以让 System.Configuration
完成繁重的工作。
关于c# - 依赖注入(inject)和 AppSettings,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3587380/