搜索文章

输入关键词开始搜索

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();
    }
}

存在的问题

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

依赖图

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;  // 把咖啡倒进杯子里
    }
}

问题:

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

有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个服务

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

推荐方案

考虑因素

  1. 包体积考虑

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

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

// 渐进式引入 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')
        };
    }
}
  1. 扩展性示例
// 插件系统
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)

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

// 代理模式:控制对原对象的访问
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容器 = 汽车工厂(制造汽车)
  • 中介者模式 = 交通信号灯(协调汽车行驶)