阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 中介者模式(Mediator)的组件通信解耦

中介者模式(Mediator)的组件通信解耦

作者:陈川 阅读数:18350人阅读 分类: JavaScript

中介者模式是一种行为设计模式,通过封装对象间的交互逻辑来减少组件间的直接依赖。它特别适用于复杂组件通信场景,能够有效降低系统耦合度并提升可维护性。JavaScript中实现中介者模式可以优雅地处理UI组件、模块或服务之间的多对多关系。

中介者模式的核心思想

中介者模式定义了一个中介对象来封装一组对象之间的交互。原本需要直接相互引用的对象现在只需与中介者通信,从而将网状依赖转变为星型结构。这种转变带来两个显著优势:

  1. 减少对象间的直接耦合,每个对象只需知道中介者
  2. 集中控制交互逻辑,使系统行为更容易理解和修改

典型的中介者结构包含以下角色:

  • Mediator:定义同事对象到中介者的接口
  • ConcreteMediator:实现协调各同事对象的逻辑
  • Colleague:知道其中介者对象,通信时通过中介者转发

JavaScript中的实现方式

在JavaScript中实现中介者模式通常有三种主流方式:

基于事件的实现

class EventMediator {
  constructor() {
    this.events = {};
  }

  subscribe(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
  }

  publish(event, ...args) {
    if (!this.events[event]) return;
    this.events[event].forEach(callback => callback(...args));
  }
}

// 使用示例
const mediator = new EventMediator();

// 组件A订阅事件
mediator.subscribe('dataLoaded', (data) => {
  console.log('Component A received:', data);
});

// 组件B触发事件
fetch('/api/data').then(res => res.json()).then(data => {
  mediator.publish('dataLoaded', data);
});

基于命令的实现

class CommandMediator {
  constructor() {
    this.commands = new Map();
  }

  register(command, handler) {
    this.commands.set(command, handler);
  }

  execute(command, ...args) {
    if (!this.commands.has(command)) {
      throw new Error(`Command ${command} not registered`);
    }
    return this.commands.get(command)(...args);
  }
}

// 使用示例
const mediator = new CommandMediator();

// 注册命令处理器
mediator.register('updateUser', (userId, data) => {
  console.log(`Updating user ${userId} with`, data);
  return true;
});

// 执行命令
mediator.execute('updateUser', 123, { name: 'John' });

混合式实现

结合事件和命令的优势:

class AdvancedMediator {
  constructor() {
    this.events = {};
    this.commands = new Map();
  }

  // 事件部分
  on(event, callback) { /* 同事件实现 */ }
  emit(event, ...args) { /* 同事件实现 */ }

  // 命令部分
  command(name, handler) { /* 同命令实现 */ }
  exec(name, ...args) { /* 同命令实现 */ }

  // 新增请求/响应模式
  request(type, data) {
    return new Promise((resolve) => {
      const requestId = Math.random().toString(36).substr(2);
      this.once(`${type}.${requestId}`, resolve);
      this.emit(type, { requestId, data });
    });
  }
}

实际应用场景分析

复杂表单验证

考虑一个用户注册表单,包含用户名、邮箱、密码和确认密码字段,各字段需要实时验证且存在跨字段校验逻辑:

class FormMediator {
  constructor() {
    this.fields = {};
    this.errors = {};
  }

  registerField(name, validateFn) {
    this.fields[name] = validateFn;
  }

  validateField(name, value) {
    const error = this.fields[name](value);
    this.errors[name] = error;
    this.checkFormValidity();
    return error;
  }

  checkFormValidity() {
    const isValid = Object.values(this.errors).every(err => !err);
    this.emit('validityChange', isValid);
  }
}

// 使用示例
const mediator = new FormMediator();

// 注册字段验证
mediator.registerField('username', (val) => {
  if (!val) return 'Required';
  if (val.length < 3) return 'Too short';
  return null;
});

mediator.registerField('password', (val) => {
  if (!val) return 'Required';
  if (val.length < 8) return 'At least 8 characters';
  return null;
});

// 字段变化时通知中介者
document.getElementById('username').addEventListener('input', (e) => {
  const error = mediator.validateField('username', e.target.value);
  showError('username', error);
});

跨组件状态同步

在电商网站中,商品列表、购物车和库存组件需要保持同步:

class ECommerceMediator {
  constructor() {
    this.products = [];
    this.cart = [];
  }

  addToCart(productId) {
    const product = this.products.find(p => p.id === productId);
    if (!product || product.stock <= 0) return false;
    
    product.stock--;
    this.cart.push({...product, quantity: 1});
    this.notifyAll();
    return true;
  }

  notifyAll() {
    this.emit('productsUpdated', this.products);
    this.emit('cartUpdated', this.cart);
  }
}

// 组件只需监听相关事件
const mediator = new ECommerceMediator();

// 商品列表组件
mediator.on('productsUpdated', (products) => {
  renderProductList(products);
});

// 购物车组件
mediator.on('cartUpdated', (cart) => {
  updateCartUI(cart);
});

性能优化与注意事项

虽然中介者模式能有效解耦,但不恰当的实现可能导致性能问题:

  1. 避免过度通知:精确控制通知范围,只在必要时广播
// 不好的做法:任何变化都全量通知
updateProduct() {
  this.product = newValue;
  this.mediator.notifyAll();
}

// 好的做法:精确通知
updateStock() {
  this.stock = newValue;
  this.mediator.notify('stockChanged', this.id, this.stock);
}
  1. 内存管理:及时清理不再需要的订阅
// 组件卸载时
componentWillUnmount() {
  this.mediator.off('eventName', this.handler);
}
  1. 循环依赖:注意中介者和同事对象间的循环引用
// 可能导致内存泄漏
class Colleague {
  constructor(mediator) {
    this.mediator = mediator;
    mediator.register(this); // 中介者又持有同事引用
  }
}

与观察者模式的区别

虽然两者都涉及对象间通信,但有本质区别:

特性 中介者模式 观察者模式
通信方向 双向(中介者与同事对象之间) 单向(主题到观察者)
耦合度 松散耦合(通过中介者间接通信) 主题知道观察者的存在
控制中心 有明确的中介者中心节点 无中心节点
适用场景 复杂网状通信关系 简单的一对多依赖关系
// 观察者模式实现
class Subject {
  constructor() {
    this.observers = [];
  }

  addObserver(obs) {
    this.observers.push(obs);
  }

  notify(data) {
    this.observers.forEach(obs => obs.update(data));
  }
}

// 中介者模式更强调协调复杂交互
class ChatRoom {
  constructor() {
    this.users = {};
  }

  register(user) {
    this.users[user.id] = user;
    user.chatroom = this;
  }

  send(message, from, to) {
    if (to) {
      this.users[to].receive(message, from);
    } else {
      Object.values(this.users).forEach(user => {
        if (user.id !== from) user.receive(message, from);
      });
    }
  }
}

在流行框架中的应用

React中的状态提升

React的状态提升本质上是简化版的中介者模式:

function ParentComponent() {
  const [sharedState, setSharedState] = useState(null);

  return (
    <>
      <ChildA 
        state={sharedState} 
        onChange={setSharedState} 
      />
      <ChildB 
        state={sharedState} 
        onReset={() => setSharedState(null)}
      />
    </>
  );
}

Vue的Event Bus

Vue2中常用的全局事件总线:

// eventBus.js
import Vue from 'vue';
export const EventBus = new Vue();

// ComponentA.vue
EventBus.$emit('message', 'Hello');

// ComponentB.vue
EventBus.$on('message', (msg) => {
  console.log(msg);
});

Angular的Service中介

Angular中常用服务作为中介者:

@Injectable({ providedIn: 'root' })
export class DataMediator {
  private dataSubject = new BehaviorSubject<any>(null);
  
  public data$ = this.dataSubject.asObservable();

  updateData(data: any) {
    this.dataSubject.next(data);
  }
}

// 组件A
constructor(private mediator: DataMediator) {}
this.mediator.updateData(newData);

// 组件B
constructor(private mediator: DataMediator) {}
this.mediator.data$.subscribe(data => this.process(data));

模式变体与高级技巧

领域特定中介者

为特定领域定制中介者接口:

class UIMediator {
  constructor() {
    this.components = {};
  }

  register(component) {
    this.components[component.name] = component;
    component.setMediator(this);
  }

  notify(sender, event, data) {
    switch(event) {
      case 'buttonClick':
        this.components.form.validate();
        break;
      case 'validationComplete':
        this.components.submitButton.setEnabled(data.isValid);
        break;
      // 其他UI交互逻辑
    }
  }
}

中间件增强

为中介者添加中间件管道:

class PipelineMediator {
  constructor() {
    this.middlewares = [];
  }

  use(middleware) {
    this.middlewares.push(middleware);
  }

  async execute(event, data) {
    let current = 0;
    const next = async () => {
      if (current < this.middlewares.length) {
        const middleware = this.middlewares[current++];
        await middleware(event, data, next);
      }
    };
    await next();
  }
}

// 使用中间件
mediator.use(async (event, data, next) => {
  console.log(`Logging event: ${event}`);
  await next();
  console.log(`Event ${event} processed`);
});

分布式中介者

适用于微前端架构的跨应用通信:

class CrossAppMediator {
  constructor() {
    window.__MEDIATOR_CHANNEL__ = new BroadcastChannel('mediator');
  }

  publish(event, data) {
    window.__MEDIATOR_CHANNEL__.postMessage({ event, data });
  }

  subscribe(event, callback) {
    const handler = (e) => {
      if (e.data.event === event) callback(e.data.data);
    };
    window.__MEDIATOR_CHANNEL__.addEventListener('message', handler);
    return () => {
      window.__MEDIATOR_CHANNEL__.removeEventListener('message', handler);
    };
  }
}

测试策略

中介者模式的测试应关注两方面:

  1. 中介者本身:验证消息路由和转换逻辑
test('should route messages correctly', () => {
  const mediator = new TestMediator();
  const mockA = { receive: jest.fn() };
  const mockB = { receive: jest.fn() };
  
  mediator.register('A', mockA);
  mediator.register('B', mockB);
  
  mediator.send('A', 'B', 'hello');
  
  expect(mockB.receive).toHaveBeenCalledWith('hello', 'A');
  expect(mockA.receive).not.toHaveBeenCalled();
});
  1. 同事对象:验证是否正确使用中介者
test('should use mediator for communication', () => {
  const mockMediator = { send: jest.fn() };
  const component = new Component(mockMediator);
  
  component.handleClick();
  
  expect(mockMediator.send).toHaveBeenCalledWith(
    'Component', 
    'TargetComponent', 
    'clickEvent'
  );
});

反模式与误用

虽然中介者模式功能强大,但以下情况可能导致问题:

  1. 上帝对象:中介者承担过多职责
// 反模式:中介者知道太多细节
class BadMediator {
  handleUserAction() {
    if (this.uiState === 'login') {
      // 处理10种不同的登录逻辑
    } else if (this.uiState === 'checkout') {
      // 处理15种结账情况
    }
    // 其他50个条件分支...
  }
}
  1. 过度设计:简单场景使用中介者
// 不必要的复杂化
const mediator = new Mediator();
mediator.register('button', {
  onClick: () => mediator.notify('buttonClick')
});
mediator.register('text', {
  update: (msg) => console.log(msg)
});

// 简单直接的方式更好
button.addEventListener('click', () => {
  textElement.textContent = 'Clicked';
});
  1. 性能瓶颈:高频消息导致中介者过载
// 高频事件可能导致性能问题
window.addEventListener('mousemove', (e) => {
  mediator.publish('mouseMove', e); // 可能触发大量后续处理
});

// 更好的做法:节流或直接处理
const throttlePublish = _.throttle(
  (e) => mediator.publish('mouseMove', e), 
  100
);

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌