IoC容器设计分析
目录
当前架构分析
依赖关系现状
// 当前的依赖注入方式(手动)
class TreemapHeat {
constructor(data, option) {
// 硬编码依赖
this.dataManager = new DataManager(option);
this.renderEngine = new RenderEngine(option, new ResourceManager());
this.interactionHandler = new InteractionHandler(option);
this.stateManager = new StateManager();
// 手动连接
this.renderEngine.resourceManager = this.resourceManager;
this.interactionHandler.container = this.renderEngine.getContainer();
}
}
存在的问题
- 紧耦合:管理器之间直接依赖
- 测试困难:难以模拟依赖进行单元测试
- 扩展性差:添加新功能需要修改现有代码
- 配置分散:配置项在多个管理器间重复
依赖图
DataManager → RenderEngine → InteractionHandler
↓ ↓ ↓
StateManager ← ResourceManager ← EventSystem
IoC容器适用性评估
适合引入IoC的理由
1. 复杂的管理器网络
- 多个管理器之间存在复杂的依赖关系
- 需要统一管理这些依赖关系
2. 多配置来源
- 用户传入的option
- 默认配置
- 环境特定配置
- 动态运行时配置
3. 扩展性需求
- 不同的渲染引擎(ZRender/PixiJS/Canvas)
- 多种布局算法
- 可插拔的交互处理器
- 自定义数据处理器
4. 测试需求
// 当前测试困难
it('should handle zoom events', () => {
const handler = new InteractionHandler(config);
// 难以模拟 RenderEngine 和 StateManager
});
// IoC 后可以轻松测试
it('should handle zoom events', () => {
const mockRenderEngine = mock<RenderEngine>();
const mockStateManager = mock<StateManager>();
const handler = container.resolve<InteractionHandler>();
// 可以完全控制依赖
});
具体实现方案
方案一:轻量级DI容器(推荐)
// 自定义轻量级容器
class TreemapContainer {
constructor() {
this.services = new Map();
this.singletons = new Map();
this.factories = new Map();
}
// 注册单例
registerSingleton(key, factory) {
this.factories.set(key, factory);
return this;
}
// 注册实例
registerInstance(key, instance) {
this.services.set(key, instance);
return this;
}
// 解析依赖
resolve(key) {
if (this.services.has(key)) {
return this.services.get(key);
}
if (this.singletons.has(key)) {
return this.singletons.get(key);
}
if (this.factories.has(key)) {
const instance = this.factories.get(key)(this);
this.singletons.set(key, instance);
return instance;
}
throw new Error(`Service ${key} not found`);
}
// 创建子容器
createChild() {
const child = new TreemapContainer();
child.services = new Map(this.services);
child.factories = new Map(this.factories);
return child;
}
}
// 使用示例
const container = new TreemapContainer()
.registerSingleton('config', () => merge(defaultConfig, userConfig))
.registerSingleton('stateManager', (c) => new StateManager(c.resolve('config')))
.registerSingleton('resourceManager', () => new ResourceManager())
.registerSingleton('renderEngine', (c) => new RenderEngine(
c.resolve('config'),
c.resolve('resourceManager')
))
.registerSingleton('dataManager', (c) => new DataManager(c.resolve('config')))
.registerSingleton('interactionHandler', (c) => new InteractionHandler(c.resolve('config')));
// 创建组件实例
class TreemapHeat {
constructor(data, option, container = null) {
this.container = container || this.createDefaultContainer(option);
this.data = data;
// 通过容器解析所有依赖
this.stateManager = this.container.resolve('stateManager');
this.renderEngine = this.container.resolve('renderEngine');
this.dataManager = this.container.resolve('dataManager');
this.interactionHandler = this.container.resolve('interactionHandler');
this.initialize();
}
createDefaultContainer(option) {
return new TreemapContainer()
.registerInstance('config', merge(defaultConfig, option))
.registerSingleton('stateManager', (c) => new StateManager(c.resolve('config')))
// ... 其他服务注册
}
}
方案二:Inversify.js完整实现
// 1. 定义接口和标识
const TYPES = {
Config: Symbol.for('Config'),
StateManager: Symbol.for('StateManager'),
RenderEngine: Symbol.for('RenderEngine'),
DataManager: Symbol.for('DataManager'),
InteractionHandler: Symbol.for('InteractionHandler'),
ResourceManager: Symbol.for('ResourceManager')
};
// 2. 定义接口
interface IConfig {
width: number;
height: number;
scale: [number, number];
// ... 其他配置
}
interface IStateManager {
getViewState(): ViewState;
updateViewState(updates: Partial<ViewState>): void;
// ... 其他方法
}
// 3. 装饰器实现
@injectable()
class StateManager implements IStateManager {
private viewState: ViewState;
constructor(
@inject(TYPES.Config) private config: IConfig
) {
this.viewState = {
scale: config.scale[0],
position: [0, 0]
};
}
getViewState(): ViewState {
return { ...this.viewState };
}
updateViewState(updates: Partial<ViewState>): void {
// 实现
}
}
// 4. 绑定配置
const container = new Container();
container.bind<IConfig>(TYPES.Config).toConstantValue(config);
container.bind<IStateManager>(TYPES.StateManager).to(StateManager).inSingletonScope();
container.bind<IRenderEngine>(TYPES.RenderEngine).to(RenderEngine).inSingletonScope();
container.bind<IDataManager>(TYPES.DataManager).to(DataManager).inSingletonScope();
container.bind<IInteractionHandler>(TYPES.InteractionHandler).to(InteractionHandler).inSingletonScope();
// 5. 主类使用
@injectable()
class TreemapHeat {
constructor(
@inject(TYPES.StateManager) private stateManager: IStateManager,
@inject(TYPES.RenderEngine) private renderEngine: IRenderEngine,
@inject(TYPES.DataManager) private dataManager: IDataManager,
@inject(TYPES.InteractionHandler) private interactionHandler: IInteractionHandler
) {}
}
方案三:函数式DI(最小化)
// 函数式依赖注入
const createTreemapServices = (config) => {
// 创建服务工厂
const createResourceManager = () => new ResourceManager();
const createStateManager = () => new StateManager(config);
const createDataManager = () => new DataManager(config);
const createRenderEngine = (resourceManager) =>
new RenderEngine(config, resourceManager);
const createInteractionHandler = () =>
new InteractionHandler(config);
// 依赖图
const resourceManager = createResourceManager();
const stateManager = createStateManager();
const dataManager = createDataManager();
const renderEngine = createRenderEngine(resourceManager);
const interactionHandler = createInteractionHandler();
// 返回服务集合
return {
config,
resourceManager,
stateManager,
dataManager,
renderEngine,
interactionHandler
};
};
// 使用
class TreemapHeat {
constructor(data, option) {
const services = createTreemapServices(option);
Object.assign(this, services);
this.data = data;
this.initialize();
}
}
小白教程:IoC带来的好处
没有IoC时的问题(现在的代码)
比喻:手工制作咖啡
// 现在的做法:每个组件自己"找"依赖
class TreemapHeat {
constructor() {
// 像手工咖啡一样,每一步都要自己做
this.dataManager = new DataManager(option); // 自己磨咖啡豆
this.renderEngine = new RenderEngine(option); // 自己煮水
this.interactionHandler = new InteractionHandler(option); // 自己冲泡
// 还要手动连接它们
this.renderEngine.dataManager = this.dataManager; // 把咖啡倒进杯子里
}
}
问题:
- 想换咖啡豆? 要改代码
new DataManager() - 想测试? 无法提供假咖啡豆
- 想加糖? 要在多个地方改代码
- 依赖混乱:谁依赖谁,看不出来
有IoC后的好处(方案一)
比喻:咖啡店点单系统
// IoC容器就像咖啡店点单台
class TreemapContainer {
constructor() {
// 提前准备好所有"材料"
this.services = new Map();
}
// 注册"菜单"
registerSingleton(key, factory) {
this.factories.set(key, factory);
}
// 取"咖啡"
resolve(key) {
// 按需制作,保证质量
return this.factories.get(key)(this);
}
}
// 使用方式
const container = new TreemapContainer()
.registerSingleton('dataManager', () => new DataManager(option)) // 菜单:美式咖啡
.registerSingleton('renderEngine', () => new RenderEngine(option)); // 菜单:拿铁
class TreemapHeat {
constructor(container) {
// 不用自己做了,直接点单
this.dataManager = container.resolve('dataManager');
this.renderEngine = container.resolve('renderEngine');
}
}
IoC如何解决四个核心问题
1. 如何解决架构分裂?
现在的问题:
// 旧架构
class TreemapHeat {
constructor() {
this.dataManager = new DataManager(); // 硬编码
}
}
// 新架构(managers目录)
class DataManager {
constructor(config) {
// 又是一个新的DataManager
}
}
IoC解决方案:
// 统一的"菜单",新旧架构都可以注册
const container = new TreemapContainer()
.registerSingleton('dataManager', () => {
// 想用旧的还是新的,在这里决定
return useNewArchitecture ? new NewDataManager() : new OldDataManager();
});
// 主类不用关心用哪个
class TreemapHeat {
constructor(container) {
this.dataManager = container.resolve('dataManager'); // 统一接口
}
}
通俗理解: 就像餐厅菜单,不管厨房怎么换(新旧架构),顾客点的都是”美式咖啡”,不用关心怎么做。
2. 如何提升可测试性?
现在的问题:
// 测试困难
it('should render correctly', () => {
const treemap = new TreemapHeat(data, option);
// DataManager 会真实调用数据,很难测试
// 如果网络有问题,测试就失败了
});
IoC解决方案:
// 可以轻松提供"假数据"
it('should render correctly', () => {
// 创建一个测试用的容器
const testContainer = new TreemapContainer()
.registerSingleton('dataManager', () => new FakeDataManager()); // 假数据管理器
const treemap = new TreemapHeat(testContainer);
// 现在测试完全可控!
});
通俗理解: 就像电影拍摄,可以用假道具测试,不用每次都用真品(节省时间,避免意外)。
3. 如何增强扩展性?
现在的问题:
// 想加新功能?要改原代码
class TreemapHeat {
constructor() {
this.dataManager = new DataManager(); // 硬编码,想换就难了
// 想加个日志功能?
// 想加个数据分析功能?
// 都要改这里的代码
}
}
IoC解决方案:
// 插件系统:像手机App一样,随时安装
class AnalyticsPlugin {
apply(container) {
container.registerSingleton('analytics', () => new AnalyticsService());
}
}
// 使用时
const container = new TreemapContainer();
new AnalyticsPlugin().apply(container); // 安装分析插件
new LoggingPlugin().apply(container); // 安装日志插件
// 主类完全不用改
const treemap = new TreemapHeat(container);
通俗理解: 就像手机,不用重装系统就能装新App,想用就装,不用就卸载。
4. 如何改善维护性?
现在的问题:
// 依赖关系看不出来
class TreemapHeat {
constructor() {
this.dataManager = new DataManager();
this.renderEngine = new RenderEngine(this.dataManager); // 隐藏的依赖
this.interactionHandler = new InteractionHandler(); // 不知道它依赖什么
}
}
IoC解决方案:
// 所有依赖都在一个地方看得清清楚楚
const container = new TreemapContainer()
.registerSingleton('config', () => config) // 配置
.registerSingleton('dataManager', () => new DataManager()) // 数据管理
.registerSingleton('renderEngine', () => new RenderEngine()) // 渲染引擎
.registerSingleton('interactionHandler', () => new InteractionHandler()); // 交互处理
// 一目了然:这个组件需要这4个服务
通俗理解: 就像乐高说明书,所有零件和步骤都列出来,不用猜要用什么。
推荐方案
考虑因素
-
包体积考虑
- 当前打包后 ~100KB,Inversify.js 会增加 50% 体积
- 轻量级容器 < 5KB,影响可忽略
-
学习成本
- 团队成员无需学习新的装饰器语法
- 保持现有的编程范式
-
渐进式迁移
// 渐进式引入 DI
class TreemapHeat {
constructor(data, option) {
// 第一阶段:保持兼容
if (option.container) {
this.container = option.container;
} else {
this.container = this.createDefaultContainer(option);
}
// 第二阶段:通过容器解析依赖
this.services = {
stateManager: this.container.resolve('stateManager'),
renderEngine: this.container.resolve('renderEngine'),
dataManager: this.container.resolve('dataManager'),
interactionHandler: this.container.resolve('interactionHandler')
};
}
}
- 扩展性示例
// 插件系统
class TreemapPlugin {
constructor(name, setup) {
this.name = name;
this.setup = setup;
}
apply(container) {
this.setup(container);
}
}
// 使用插件扩展
const container = new TreemapContainer();
const pixiPlugin = new TreemapPlugin('pixi-renderer', (container) => {
container.registerSingleton('renderEngine', (c) => new PixiRenderEngine(c.resolve('config')));
});
const analyticsPlugin = new TreemapPlugin('analytics', (container) => {
container.registerSingleton('analytics', (c) => new AnalyticsService(c.resolve('stateManager')));
});
[pixiPlugin, analyticsPlugin].forEach(plugin => plugin.apply(container));
最终推荐:方案一(轻量级DI容器)
核心理由:
- 解决架构分裂:统一新旧架构的依赖管理
- 提升可测试性:可以轻松模拟依赖进行单元测试
- 增强扩展性:支持插件系统和运行时替换组件
- 改善维护性:依赖关系清晰,降低耦合度
- 包体积小(< 5KB),对库类项目友好
- 学习成本低,渐进式迁移
总结
是否适合引入IoC:强烈推荐 ✅
核心价值:
- 解决架构分裂:统一新旧架构的依赖管理
- 提升可测试性:可以轻松模拟依赖进行单元测试
- 增强扩展性:支持插件系统和运行时替换组件
- 改善维护性:依赖关系清晰,降低耦合度
建议方案:
- 首选:自定义轻量级DI容器(< 5KB)
- 备选:函数式DI模式(< 1KB)
- 不推荐:Inversify.js(包体积太大)
实施步骤:
- 先实现轻量级容器
- 逐步迁移管理器到容器管理
- 支持插件扩展机制
- 完善测试覆盖
简单理解:
- 没有IoC:像手工作坊,每样东西都要自己做
- 有IoC:像现代化工厂,标准化零件,即插即用
这样的重构将显著提升组件的架构质量和可维护性!
IoC vs 代理模式
常见误解
很多人容易混淆IoC和代理模式,认为IoC就是代理的概念。实际上它们是解决不同问题的不同模式。
代理模式(Proxy Pattern)
代理是结构型设计模式,核心是控制访问:
// 代理模式:控制对原对象的访问
class ImageProxy {
constructor(realImage) {
this.realImage = realImage;
}
display() {
// 代理可以添加额外逻辑
console.log('加载中...');
this.realImage.display();
console.log('加载完成');
}
}
// 使用
const realImage = new RealImage();
const proxy = new ImageProxy(realImage);
proxy.display(); // 通过代理访问
代理的特点:
- 代理和原对象实现相同接口
- 代理包装原对象
- 主要用于:延迟加载、访问控制、缓存、日志等
IoC容器(控制反转)
IoC是架构原则,核心是依赖管理:
// IoC容器:管理对象创建和依赖关系
class Container {
constructor() {
this.services = new Map();
}
register(key, factory) {
this.services.set(key, factory);
}
resolve(key) {
return this.services.get(key)(this);
}
}
// 使用
const container = new Container();
container.register('database', () => new Database());
container.register('userService', (c) => new UserService(c.resolve('database')));
const userService = container.resolve('userService');
IoC的特点:
- 容器管理对象生命周期
- 处理依赖注入
- 关注整体架构,而非单个对象
关键区别对比
| 特征 | 代理模式 | IoC容器 |
|---|---|---|
| 目的 | 控制对单个对象的访问 | 管理整个应用的依赖关系 |
| 范围 | 对象级别 | 应用级别 |
| 关系 | 1:1(一个代理包装一个对象) | 1:N(容器管理多个对象) |
| 主要功能 | 访问控制、延迟加载 | 依赖注入、生命周期管理 |
| 使用场景 | 需要拦截方法调用时 | 需要解耦依赖时 |
实际例子对比
代理模式的典型应用
// 1. 网络请求代理
class ApiProxy {
constructor(realApi) {
this.realApi = realApi;
this.cache = new Map();
}
async fetchData(url) {
if (this.cache.has(url)) {
return this.cache.get(url); // 缓存功能
}
const data = await this.realApi.fetchData(url);
this.cache.set(url, data);
return data;
}
}
// 2. 权限控制代理
class UserProxy {
constructor(realUser) {
this.realUser = realUser;
}
deletePost(postId) {
if (this.realUser.isAdmin) {
return this.realUser.deletePost(postId);
}
throw new Error('无权限删除');
}
}
IoC容器的典型应用
// 1. 依赖注入
class UserService {
constructor(database, logger) {
this.database = database; // 由容器注入
this.logger = logger; // 由容器注入
}
}
// 2. 生命周期管理
class AppContainer {
constructor() {
this.services = new Map();
this.singletons = new Map();
}
// 单例模式
resolve(key) {
if (!this.singletons.has(key)) {
this.singletons.set(key, this.services.get(key)(this));
}
return this.singletons.get(key);
}
}
它们的关系
IoC容器内部可能会使用代理模式,但这是两个不同的概念:
class AdvancedContainer {
createProxy(service, key) {
// 容器内部使用代理来增强服务
return new Proxy(service, {
get(target, prop) {
if (prop === 'dispose') {
return () => this.disposeService(key);
}
return target[prop];
}
});
}
disposeService(key) {
// 清理资源
console.log(`Disposing service: ${key}`);
}
}
简单总结
代理模式 = 门卫
- 只管一扇门的进出
- 可以检查证件、记录日志
- 但是不管整个小区的管理
IoC容器 = 物业管理公司
- 管理整个小区的所有服务
- 负责服务之间的协调
- 处理服务的创建和销毁
所以,IoC不是代理的概念,它们是解决不同问题的不同模式:
- 代理解决访问控制问题
- IoC解决依赖管理问题
在刚才的Treemap组件中,我们用IoC是为了解决多个管理器之间的依赖关系混乱问题,而不是为了控制对某个对象的访问。
DI和IoC的关系和区别
核心关系
DI(依赖注入)是IoC(控制反转)的一种实现方式
可以这样理解:
- IoC 是一种设计原则/理念
- DI 是一种具体的实现技术
比喻说明
IoC(控制反转)= 转让控制权
- 就像雇佣司机:你不再自己开车(反转控制)
- 但具体怎么实现?可以有多种方式
DI(依赖注入)= 司机自己拿钥匙
- 司机主动获取钥匙(依赖注入)
- 这是一种实现”不自己开车”的具体方式
具体区别
IoC(控制反转)
定义:将对象创建和依赖关系的管理从代码内部转移到外部容器
核心思想:好莱坞原则 - “Don’t call us, we’ll call you”
// 传统方式:主动创建依赖
class UserService {
constructor() {
this.database = new Database(); // 自己控制创建
}
}
// IoC方式:被动接受依赖
class UserService {
constructor(database) { // 由外部注入
this.database = database;
}
}
DI(依赖注入)
定义:一种实现IoC的具体技术,通过外部注入依赖而不是内部创建
三种注入方式:
1. 构造函数注入(推荐)
class UserService {
constructor(database, logger) {
this.database = database;
this.logger = logger;
}
}
// 使用
const userService = new UserService(database, logger);
2. 属性注入
class UserService {
setDatabase(database) {
this.database = database;
}
setLogger(logger) {
this.logger = logger;
}
}
// 使用
const userService = new UserService();
userService.setDatabase(database);
userService.setLogger(logger);
3. 接口注入(较少使用)
interface Injectable {
inject(dependencies: object): void;
}
class UserService implements Injectable {
inject({ database, logger }) {
this.database = database;
this.logger = logger;
}
}
更完整的例子
没有使用IoC/DI
// 硬编码依赖,紧耦合
class OrderService {
constructor() {
this.database = new MySQLDatabase(); // 硬编码
this.logger = new FileLogger(); // 硬编码
this.payment = new AlipayService(); // 硬编码
}
createOrder(orderData) {
// 业务逻辑
}
}
使用DI(实现了IoC)
// 依赖通过构造函数注入
class OrderService {
constructor(database, logger, payment) {
this.database = database; // 不关心具体实现
this.logger = logger; // 不关心具体实现
this.payment = payment; // 不关心具体实现
}
createOrder(orderData) {
// 业务逻辑
}
}
// 创建服务时可以灵活替换依赖
const orderService1 = new OrderService(
new MySQLDatabase(),
new FileLogger(),
new AlipayService()
);
const orderService2 = new OrderService(
new PostgreSQLDatabase(), // 替换数据库
new ConsoleLogger(), // 替换日志器
new WechatPayService() // 替换支付服务
);
使用DI容器(更完整的IoC实现)
// DI容器自动管理依赖
class DIContainer {
constructor() {
this.services = new Map();
}
register(key, factory) {
this.services.set(key, factory);
}
resolve(key) {
const factory = this.services.get(key);
return factory(this);
}
}
// 注册服务
const container = new DIContainer();
container.register('database', () => new MySQLDatabase());
container.register('logger', () => new FileLogger());
container.register('payment', () => new AlipayService());
container.register('orderService', (c) =>
new OrderService(
c.resolve('database'),
c.resolve('logger'),
c.resolve('payment')
)
);
// 使用
const orderService = container.resolve('orderService');
关系总结
概念层级
IoC(控制反转)- 设计原则
↓
DI(依赖注入)- 实现方式
↓
DI容器 - 工具实现
其他IoC实现方式
除了DI,IoC还有其他实现方式:
1. 服务定位器(Service Locator)
// 也是IoC,但不是DI
class ServiceLocator {
static getDatabase() {
return this.database;
}
static getLogger() {
return this.logger;
}
}
class UserService {
constructor() {
// 主动获取依赖(不是注入)
this.database = ServiceLocator.getDatabase();
this.logger = ServiceLocator.getLogger();
}
}
2. 事件驱动
// 通过事件实现IoC
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
emit(event, data) {
if (this.events[event]) {
this.events[event].forEach(callback => callback(data));
}
}
}
// 对象不直接调用,而是通过事件通信
const emitter = new EventEmitter();
emitter.on('user.created', (user) => {
console.log('User created:', user);
});
// 触发事件(控制反转)
emitter.emit('user.created', { id: 1, name: 'John' });
实际应用中的选择
何时使用DI
- 需要解耦组件依赖
- 要求良好的可测试性
- 项目规模较大,依赖关系复杂
何时使用Service Locator
- 老旧系统改造
- 某些特定场景(如插件系统)
- 不推荐在新项目中使用
何时使用事件驱动
- 松耦合的通信需求
- 异步处理
- 插件系统
简单总结
IoC = 哲学思想
- 核心是”反转控制”
- 不自己创建和管理依赖
DI = 具体技术
- 通过外部注入依赖
- 是实现IoC的一种方式
关系:
- IoC是”做什么”(What)
- DI是”怎么做”(How)
- DI是实现IoC最流行的方式
就像:
- 健康生活是IoC(原则)
- 健身是DI(实现方式之一)
- 还有其他实现方式如饮食控制、规律作息等
IoC vs 中介者模式
核心区别
IoC(控制反转) 是一种架构原则,关注的是依赖管理和控制权转移。
中介者模式 是一种行为型设计模式,关注的是对象间通信和解耦。
中介者模式的特点
中介者模式的核心是集中管理对象间的交互:
// 中介者模式:对象间通信的中介
class ChatRoomMediator {
constructor() {
this.users = [];
}
register(user) {
this.users.push(user);
user.setMediator(this);
}
send(message, fromUser) {
this.users.forEach(user => {
if (user !== fromUser) {
user.receive(message);
}
});
}
}
class User {
constructor(name) {
this.name = name;
this.mediator = null;
}
setMediator(mediator) {
this.mediator = mediator;
}
send(message) {
this.mediator.send(message, this);
}
receive(message) {
console.log(`${this.name} received: ${message}`);
}
}
// 使用
const mediator = new ChatRoomMediator();
const user1 = new User('Alice');
const user2 = new User('Bob');
mediator.register(user1);
mediator.register(user2);
user1.send('Hello Bob'); // 通过中介者通信
IoC容器的特点
IoC容器关注的是依赖注入和生命周期管理:
// IoC容器:依赖管理
class Container {
constructor() {
this.services = new Map();
}
register(key, factory) {
this.services.set(key, factory);
}
resolve(key) {
return this.services.get(key)(this);
}
}
class ServiceA {
constructor(serviceB) {
this.serviceB = serviceB; // 依赖注入
}
}
class ServiceB {
constructor() {}
}
// 使用
const container = new Container();
container.register('serviceB', () => new ServiceB());
container.register('serviceA', (c) => new ServiceA(c.resolve('serviceB')));
const serviceA = container.resolve('serviceA');
关键区别对比
| 特征 | IoC容器 | 中介者模式 |
|---|---|---|
| 目的 | 管理依赖注入和对象生命周期 | 管理对象间的通信和交互 |
| 关注点 | 对象创建和依赖关系 | 对象间的消息传递 |
| 关系 | 1:N(容器管理多个服务) | N:N(多个对象通过中介者通信) |
| 通信方式 | 不直接处理对象间通信 | 专门处理对象间通信 |
| 实现时机 | 对象创建时 | 对象运行时 |
| 典型应用 | 依赖注入、服务定位 | 聊天室、事件系统、UI组件协调 |
实际应用场景
中介者模式的典型应用
// 1. 聊天室系统
class ChatMediator {
// ... 如上所示
}
// 2. 表单验证协调
class FormMediator {
constructor() {
this.fields = [];
}
registerField(field) {
this.fields.push(field);
field.on('change', () => this.validateForm());
}
validateForm() {
const isValid = this.fields.every(field => field.isValid());
this.emit('formValidation', isValid);
}
}
// 3. 交通信号灯协调
class TrafficMediator {
constructor() {
this.lights = [];
}
registerLight(light) {
this.lights.push(light);
}
changeLight(changedLight) {
// 协调其他信号灯
this.lights.forEach(light => {
if (light !== changedLight) {
light协调状态(changedLight);
}
});
}
}
IoC容器的典型应用
// 1. 服务依赖管理
class UserService {
constructor(database, cache, logger) {
// 依赖注入
this.database = database;
this.cache = cache;
this.logger = logger;
}
}
// 2. 插件系统
class PluginManager {
constructor(container) {
this.container = container;
this.plugins = [];
}
loadPlugin(pluginClass) {
const plugin = new pluginClass(this.container);
this.plugins.push(plugin);
}
}
// 3. 配置管理
class ConfigManager {
constructor(container) {
this.config = container.resolve('config');
}
}
混合使用的情况
在实际项目中,IoC容器和中介者模式经常结合使用:
// IoC容器 + 中介者模式
class Container {
constructor() {
this.services = new Map();
}
// ... IoC容器功能
}
class EventBus {
constructor() {
this.handlers = new Map();
}
// 中介者功能:事件处理
on(event, handler) {
if (!this.handlers.has(event)) {
this.handlers.set(event, []);
}
this.handlers.get(event).push(handler);
}
emit(event, data) {
if (this.handlers.has(event)) {
this.handlers.get(event).forEach(handler => handler(data));
}
}
}
// 使用IoC容器管理中介者
const container = new Container();
container.register('eventBus', () => new EventBus());
container.register('serviceA', (c) => {
const service = new ServiceA();
service.eventBus = c.resolve('eventBus');
return service;
});
// ServiceA通过中介者(EventBus)与其他服务通信
class ServiceA {
constructor() {
this.eventBus = null; // 由IoC容器注入
}
doSomething() {
// 通过中介者通信,而不是直接调用
this.eventBus.emit('serviceA.action', { data: 'something' });
}
}
在Treemap组件中的应用
在我们的Treemap组件中:
IoC容器的使用
// IoC容器管理依赖
class TreemapContainer {
constructor() {
this.services = new Map();
}
register(key, factory) {
this.services.set(key, factory);
}
resolve(key) {
return this.services.get(key)(this);
}
}
// 管理各个服务
const container = new TreemapContainer()
.register('stateManager', () => new StateManager())
.register('renderEngine', () => new RenderEngine())
.register('dataManager', () => new DataManager());
中介者模式的使用
// 中介者模式管理服务间通信
class ServiceMediator {
constructor() {
this.services = new Map();
this.eventBus = new EventBus();
}
registerService(name, service) {
this.services.set(name, service);
service.setMediator(this);
}
notify(service, event, data) {
// 服务间通过中介者通信
this.services.forEach((otherService, name) => {
if (otherService !== service) {
otherService.handleEvent(event, data);
}
});
}
}
// 使用中介者协调服务
const mediator = new ServiceMediator();
mediator.registerService('renderEngine', renderEngine);
mediator.registerService('interactionHandler', interactionHandler);
// 当交互发生时,通过中介者通知渲染引擎
interactionHandler.on('zoom', (data) => {
mediator.notify(interactionHandler, 'zoom', data);
});
总结
IoC容器不是中介者模式,它们解决不同的问题:
IoC容器 = 对象工厂 + 依赖管理器
- 关注:对象如何被创建和组装
- 解决:依赖注入和生命周期管理
- 时机:对象创建阶段
中介者模式 = 通信协调器
- 关注:对象间如何通信
- 解决:对象间的复杂交互
- 时机:对象运行阶段
在实际项目中,它们经常配合使用:
- IoC容器负责创建和管理对象
- 中介者模式负责对象间的通信协调
就像:
- IoC容器 = 汽车工厂(制造汽车)
- 中介者模式 = 交通信号灯(协调汽车行驶)