IoC避不开的一个问题是如何处理应用程序的模块化, 因为IoC通常针对单个对象提供了良好的支持, 比如依赖管理,生命周期管理,部署时配置甚至运行时配置, 但往往一组内聚的互相协作的对象才构成应用程序基本的构建块. 这组内聚对象间的协作关系是实现细节, 包括单个对象的构造函数和属性也是, 如果把这些暴露出来, 固然可以提高灵活性, 但是以最后的部署阶段的复杂性以及难以维护性为代价的. 以配置文件为例, 可能需要在包含了几百个对象定义的配置文件中, 去改动七八个属性, 变换五六个实现, 来适应不同的运行环境
两个解决这个问题的方向, 一个是IoC框架提供模块化支持, 以及部署时支持, 一种是IoC框架应该避开这个问题, 这本不属于IoC的范围, 应该交由专门的模块化框架如OSGi去解决
在我相对熟悉的IoC框架中, Autofac试图提供"Module"这个概念来解决一部分问题. 对象间的协作可以隐藏在Module对象中, 一些必要的参数可以通过Module的属性设置进来, 而对使用者,对部署只需暴露Module对象即可. 这是以丧失灵活性为代价的, 替换某个内部对象的实现变成了Hack, 需要通过团队成员间的交流, 源代码集体所有来完成. 如果在Module对象内部枚举扩展点的所有实现, 通过部署时的参数来挑选某个实现, 则会引入不必要的依赖, 更是得不偿失
个人倾向于第二种. 谁介绍一下.Net平台上的模块化框架?
所以相对聚焦一点, 了解一下IoC框架在处理单个对象的时候所需要解决的问题
1, 部署时如何方便的支持产品环境和测试环境, 即在不同环境下提供同一接口的不同实现
这个甚至不需要IoC框架解决, 应用开发人员在不同环境下提供不同配置文件即可. 那问题变成了
2, 如何简化配置, 使得框架能够自动识别依赖
这个被称为Auto wire, 一般根据构造函数参数或属性的类型来匹配依赖. 那么带来的一个问题是
3, 运行时如何动态切换同一个类型的不同实现, 比如用户选择了只读模式, 那么所有的Save操作都应被忽略不做任何事, 可以通过提供Save操作的Dummy版本来实现
Auto wire根据类型匹配有一定的局限性, 即同一个运行时环境有某个接口的多个实现怎么办. 这里实际上有一个概念, 即对象的标识. 而类型只是标识的一种. 另外一种适应性更广的标识是字符串. 有的框架称之为ID, 有的称之为name. 标识的重要特征就是在一个运行时环境里是唯一的. 类型标识因此也可以转化为字符串标识, 取其全名就可以了. (标识本身是个概念, 可以用一个类来表达, 然后有不同的子类实现, 如基于类型的标识, 基于字符串的标识等)
因此就有两种策略: 一种是允许运行时某个对象可以重新声明依赖的标识, 一种是运行时用同样标识的对象替换原先的实现. 后一种相对容易实现一点, 按注册顺序Last One Win就可以了
如果同一个类型的多个实例需要同时存在, 则部署时给予不同的ID即可. 然而另一个问题是
4, 多用户环境下, 如何保证每个用户都有自己的对象组合而不互相干扰
IoC框架一般被称为IoC容器. 容器这个概念是对运行时环境,上下文的封装, 提供了一组基础设施, 以及运行时所需的各种服务, 最重要的一点是, 它提供了在同一个进程里的隔离. 多用户环境下, 可以创建多个彼此独立的容器, 每个容器负责创建所需的对象, 提供所需的服务, 彼此之间互不影响. 于是带来新的问题,
5, 对象的生命周期如何管理
很显然, 有的对象概念上属于全局, 在程序运行过程中不能创建多个实例, 也不能被销毁. 有的跟用户的一次操作, 一次请求相关联, 同一次请求中可以复用同一个对象实例, 但不同请求必须创建不同的对象实例. 有的则每次需要访问它的时候都得创建一个新的实例. 容器必须提供基础设施, 让应用开发者可以指定对象的生命周期, 并在周期结束时销毁所有该销毁的对象. C# 提供了 Dispose 机制, 可供利用
其它的一些问题, 比如:
- 如何支持composite, decorator, proxy模式
- 如何方便用户注册, 包括自动注册
- 如何注入容器创建的对象到用户手工创建的对象中
- 如何与现有各种框架集成
- 如何避免循环引用: Constructor/property dependencies
- Open generics injection
- List injection Unregistered resolution
- Auto-mocking
- Startable
- Strongly-typed Activation Events
- Adding to an Existing Container