在软件开发过程中,预见并适应变化是至关重要的。一个灵活的应用能够提前准备好应对变化,这是其核心特性之一。IoC容器(Inversion of Control container)是一种常用的工具,它允许避免在应用中硬编码实例化逻辑,从而提高应用的灵活性和可扩展性。通过遵循契约优先设计原则,并将实例化责任交给IoC容器,可以创建出既灵活又可扩展的应用。
假设要设计一个能够将文本输出到不同输出通道的应用,例如文件输出通道和控制台输出通道。首先,可以定义一个输出契约,这样主应用就可以遵循契约来输出文本,而无需知道具体的输出通道。
public interface IOutputService
{
void WriteLine(string data);
}
定义了契约之后,可以有多种不同的实现,比如文件输出通道和控制台输出通道:
public class FileOutput : IOutputService
{
public void WriteLine(string data)
{
using (StreamWriter sw = new StreamWriter("test.txt", false))
{
sw.WriteLine(data);
}
}
}
public class ConsoleOutput : IOutputService
{
public void WriteLine(string data)
{
Console.Write(data);
}
}
在应用中获取输出通道的最简单方式是使用通道工厂来创建请求的输出通道实例。然而,这样做会在应用中硬编码实例化逻辑。如果想要添加新的输出通道,比如网络输出通道,那么唯一的选择就是更改代码。
借助IoC容器的帮助,可以轻松创建一个可插拔的应用,而无需编写大量代码。想法是定义一个基础库,它将具有如下API:
public interface IIoCContainer
{
T Resolve();
IEnumerable ResolveAll();
}
这个API为应用提供了一种获取指定类型单个实例或实例集合的方式,因此可以使用IOutputService接口来获取特定的输出通道。IoC容器API允许使用任何IoC容器,比如Unity或StructureMap。在应用启动时,使用BootStrapper来设置IoC容器。
public class BootStrapper
{
public static void StartUp()
{
RegisterIoCContainer();
}
private static void RegisterIoCContainer()
{
ObjectFactory.Initialize(x => { x.PullConfigurationFromAppConfig = true; });
IoC.IoCContainerManager.IoCContainer = new StructureMapIoCContainer();
}
}
使用BootStrapper初始化IoC容器:
BootStrapper.StartUp();
IoC容器初始化后,应用可以直接使用IoC容器API来获取IOutputService。
IEnumerable outputServices = IoC.IoCContainerManager.IoCContainer.ResolveAll();
outputServices.ToList().ForEach(o => o.WriteLine("Hello World!"));
为了使应用更加灵活,并允许在生产环境中切换不同的输出通道而无需重新编译,决定不在主应用中直接引用输出通道实现。输出通道实现将通过后构建事件命令行复制到主应用中。IOutputService与实际实现之间的映射在应用程序配置文件中定义。
<configSections>
<section name="StructureMap" type="StructureMap.Configuration.StructureMapConfigurationSection,StructureMap" />
</configSections>
<StructureMap>
<PluginFamily Type="OutputService.IOutputService" Assembly="OutputService" DefaultKey="Default">
<Plugin Type="FileOutput.FileOutput" Assembly="FileOutput" ConcreteKey="Default" />
<Plugin Type="ConsoleOutput.ConsoleOutput" Assembly="ConsoleOutput" ConcreteKey="Console" />
</PluginFamily>
</StructureMap>
当调用IoCContainer.ResolveAll