IoC容器设计分析

目录

当前架构分析

依赖关系现状

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 当前的依赖注入方式(手动)
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();
}
}

存在的问题

  1. 紧耦合:管理器之间直接依赖
  2. 测试困难:难以模拟依赖进行单元测试
  3. 扩展性差:添加新功能需要修改现有代码
  4. 配置分散:配置项在多个管理器间重复

依赖图

1
2
3
DataManager → RenderEngine → InteractionHandler
↓ ↓ ↓
StateManager ← ResourceManager ← EventSystem

IoC容器适用性评估

适合引入IoC的理由

1. 复杂的管理器网络

  • 多个管理器之间存在复杂的依赖关系
  • 需要统一管理这些依赖关系

2. 多配置来源

  • 用户传入的option
  • 默认配置
  • 环境特定配置
  • 动态运行时配置

3. 扩展性需求

  • 不同的渲染引擎(ZRender/PixiJS/Canvas)
  • 多种布局算法
  • 可插拔的交互处理器
  • 自定义数据处理器

4. 测试需求

1
2
3
4
5
6
7
8
9
10
11
12
13
// 当前测试困难
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容器(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// 自定义轻量级容器
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// 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(最小化)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 函数式依赖注入
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时的问题(现在的代码)

比喻:手工制作咖啡

1
2
3
4
5
6
7
8
9
10
11
12
// 现在的做法:每个组件自己"找"依赖
class TreemapHeat {
constructor() {
// 像手工咖啡一样,每一步都要自己做
this.dataManager = new DataManager(option); // 自己磨咖啡豆
this.renderEngine = new RenderEngine(option); // 自己煮水
this.interactionHandler = new InteractionHandler(option); // 自己冲泡

// 还要手动连接它们
this.renderEngine.dataManager = this.dataManager; // 把咖啡倒进杯子里
}
}

问题:

  1. 想换咖啡豆? 要改代码 new DataManager()
  2. 想测试? 无法提供假咖啡豆
  3. 想加糖? 要在多个地方改代码
  4. 依赖混乱:谁依赖谁,看不出来

有IoC后的好处(方案一)

比喻:咖啡店点单系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 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. 如何解决架构分裂?

现在的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 旧架构
class TreemapHeat {
constructor() {
this.dataManager = new DataManager(); // 硬编码
}
}

// 新架构(managers目录)
class DataManager {
constructor(config) {
// 又是一个新的DataManager
}
}

IoC解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 统一的"菜单",新旧架构都可以注册
const container = new TreemapContainer()
.registerSingleton('dataManager', () => {
// 想用旧的还是新的,在这里决定
return useNewArchitecture ? new NewDataManager() : new OldDataManager();
});

// 主类不用关心用哪个
class TreemapHeat {
constructor(container) {
this.dataManager = container.resolve('dataManager'); // 统一接口
}
}

通俗理解: 就像餐厅菜单,不管厨房怎么换(新旧架构),顾客点的都是”美式咖啡”,不用关心怎么做。

2. 如何提升可测试性?

现在的问题:

1
2
3
4
5
6
// 测试困难
it('should render correctly', () => {
const treemap = new TreemapHeat(data, option);
// DataManager 会真实调用数据,很难测试
// 如果网络有问题,测试就失败了
});

IoC解决方案:

1
2
3
4
5
6
7
8
9
// 可以轻松提供"假数据"
it('should render correctly', () => {
// 创建一个测试用的容器
const testContainer = new TreemapContainer()
.registerSingleton('dataManager', () => new FakeDataManager()); // 假数据管理器

const treemap = new TreemapHeat(testContainer);
// 现在测试完全可控!
});

通俗理解: 就像电影拍摄,可以用假道具测试,不用每次都用真品(节省时间,避免意外)。

3. 如何增强扩展性?

现在的问题:

1
2
3
4
5
6
7
8
9
10
// 想加新功能?要改原代码
class TreemapHeat {
constructor() {
this.dataManager = new DataManager(); // 硬编码,想换就难了

// 想加个日志功能?
// 想加个数据分析功能?
// 都要改这里的代码
}
}

IoC解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 插件系统:像手机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. 如何改善维护性?

现在的问题:

1
2
3
4
5
6
7
8
// 依赖关系看不出来
class TreemapHeat {
constructor() {
this.dataManager = new DataManager();
this.renderEngine = new RenderEngine(this.dataManager); // 隐藏的依赖
this.interactionHandler = new InteractionHandler(); // 不知道它依赖什么
}
}

IoC解决方案:

1
2
3
4
5
6
7
8
// 所有依赖都在一个地方看得清清楚楚
const container = new TreemapContainer()
.registerSingleton('config', () => config) // 配置
.registerSingleton('dataManager', () => new DataManager()) // 数据管理
.registerSingleton('renderEngine', () => new RenderEngine()) // 渲染引擎
.registerSingleton('interactionHandler', () => new InteractionHandler()); // 交互处理

// 一目了然:这个组件需要这4个服务

通俗理解: 就像乐高说明书,所有零件和步骤都列出来,不用猜要用什么。

推荐方案

考虑因素

  1. 包体积考虑

    • 当前打包后 ~100KB,Inversify.js 会增加 50% 体积
    • 轻量级容器 < 5KB,影响可忽略
  2. 学习成本

    • 团队成员无需学习新的装饰器语法
    • 保持现有的编程范式
  3. 渐进式迁移

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 渐进式引入 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')
    };
    }
    }
  4. 扩展性示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 插件系统
    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:强烈推荐 ✅

核心价值:

  1. 解决架构分裂:统一新旧架构的依赖管理
  2. 提升可测试性:可以轻松模拟依赖进行单元测试
  3. 增强扩展性:支持插件系统和运行时替换组件
  4. 改善维护性:依赖关系清晰,降低耦合度

建议方案:

  • 首选:自定义轻量级DI容器(< 5KB)
  • 备选:函数式DI模式(< 1KB)
  • 不推荐:Inversify.js(包体积太大)

实施步骤:

  1. 先实现轻量级容器
  2. 逐步迁移管理器到容器管理
  3. 支持插件扩展机制
  4. 完善测试覆盖

简单理解:

  • 没有IoC:像手工作坊,每样东西都要自己做
  • 有IoC:像现代化工厂,标准化零件,即插即用

这样的重构将显著提升组件的架构质量和可维护性!

IoC vs 代理模式

常见误解

很多人容易混淆IoC和代理模式,认为IoC就是代理的概念。实际上它们是解决不同问题的不同模式。

代理模式(Proxy Pattern)

代理是结构型设计模式,核心是控制访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 代理模式:控制对原对象的访问
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是架构原则,核心是依赖管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 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容器内部可能会使用代理模式,但这是两个不同的概念:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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”

1
2
3
4
5
6
7
8
9
10
11
12
13
// 传统方式:主动创建依赖
class UserService {
constructor() {
this.database = new Database(); // 自己控制创建
}
}

// IoC方式:被动接受依赖
class UserService {
constructor(database) { // 由外部注入
this.database = database;
}
}

DI(依赖注入)

定义:一种实现IoC的具体技术,通过外部注入依赖而不是内部创建

三种注入方式:

1. 构造函数注入(推荐)

1
2
3
4
5
6
7
8
9
class UserService {
constructor(database, logger) {
this.database = database;
this.logger = logger;
}
}

// 使用
const userService = new UserService(database, logger);

2. 属性注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class UserService {
setDatabase(database) {
this.database = database;
}

setLogger(logger) {
this.logger = logger;
}
}

// 使用
const userService = new UserService();
userService.setDatabase(database);
userService.setLogger(logger);

3. 接口注入(较少使用)

1
2
3
4
5
6
7
8
9
10
interface Injectable {
inject(dependencies: object): void;
}

class UserService implements Injectable {
inject({ database, logger }) {
this.database = database;
this.logger = logger;
}
}

更完整的例子

没有使用IoC/DI

1
2
3
4
5
6
7
8
9
10
11
12
// 硬编码依赖,紧耦合
class OrderService {
constructor() {
this.database = new MySQLDatabase(); // 硬编码
this.logger = new FileLogger(); // 硬编码
this.payment = new AlipayService(); // 硬编码
}

createOrder(orderData) {
// 业务逻辑
}
}

使用DI(实现了IoC)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 依赖通过构造函数注入
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实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 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');

关系总结

概念层级

1
2
3
4
5
IoC(控制反转)- 设计原则

DI(依赖注入)- 实现方式

DI容器 - 工具实现

其他IoC实现方式

除了DI,IoC还有其他实现方式:

1. 服务定位器(Service Locator)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 也是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. 事件驱动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 通过事件实现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(控制反转) 是一种架构原则,关注的是依赖管理控制权转移

中介者模式 是一种行为型设计模式,关注的是对象间通信解耦

中介者模式的特点

中介者模式的核心是集中管理对象间的交互

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 中介者模式:对象间通信的中介
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容器关注的是依赖注入生命周期管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 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容器和中介者模式经常结合使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 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容器的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 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());

中介者模式的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 中介者模式管理服务间通信
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容器 = 汽车工厂(制造汽车)
  • 中介者模式 = 交通信号灯(协调汽车行驶)