中介者模式(Mediator)的组件通信解耦
中介者模式是一种行为设计模式,通过封装对象间的交互逻辑来减少组件间的直接依赖。它特别适用于复杂组件通信场景,能够有效降低系统耦合度并提升可维护性。JavaScript中实现中介者模式可以优雅地处理UI组件、模块或服务之间的多对多关系。
中介者模式的核心思想
中介者模式定义了一个中介对象来封装一组对象之间的交互。原本需要直接相互引用的对象现在只需与中介者通信,从而将网状依赖转变为星型结构。这种转变带来两个显著优势:
- 减少对象间的直接耦合,每个对象只需知道中介者
- 集中控制交互逻辑,使系统行为更容易理解和修改
典型的中介者结构包含以下角色:
- 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);
});
性能优化与注意事项
虽然中介者模式能有效解耦,但不恰当的实现可能导致性能问题:
- 避免过度通知:精确控制通知范围,只在必要时广播
// 不好的做法:任何变化都全量通知
updateProduct() {
this.product = newValue;
this.mediator.notifyAll();
}
// 好的做法:精确通知
updateStock() {
this.stock = newValue;
this.mediator.notify('stockChanged', this.id, this.stock);
}
- 内存管理:及时清理不再需要的订阅
// 组件卸载时
componentWillUnmount() {
this.mediator.off('eventName', this.handler);
}
- 循环依赖:注意中介者和同事对象间的循环引用
// 可能导致内存泄漏
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);
};
}
}
测试策略
中介者模式的测试应关注两方面:
- 中介者本身:验证消息路由和转换逻辑
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();
});
- 同事对象:验证是否正确使用中介者
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'
);
});
反模式与误用
虽然中介者模式功能强大,但以下情况可能导致问题:
- 上帝对象:中介者承担过多职责
// 反模式:中介者知道太多细节
class BadMediator {
handleUserAction() {
if (this.uiState === 'login') {
// 处理10种不同的登录逻辑
} else if (this.uiState === 'checkout') {
// 处理15种结账情况
}
// 其他50个条件分支...
}
}
- 过度设计:简单场景使用中介者
// 不必要的复杂化
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';
});
- 性能瓶颈:高频消息导致中介者过载
// 高频事件可能导致性能问题
window.addEventListener('mousemove', (e) => {
mediator.publish('mouseMove', e); // 可能触发大量后续处理
});
// 更好的做法:节流或直接处理
const throttlePublish = _.throttle(
(e) => mediator.publish('mouseMove', e),
100
);
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn