中介者模式核心解析
- 中介者模式 (Mediator Pattern):中介者 = 中控室 / 调度中心,所有组件只跟中介说话,不互相说话
- 核心是引入一个 “中介者” 对象,让原本互相耦合、直接通信的多个对象 (同事类),改为通过中介者间接通信;
- 可以类比生活场景:
- 无中介者:公司里每个员工直接私聊沟通工作,10 个员工需要建立 45 组私聊 (N*(N-1)/2),一人离职要通知所有人;
- 有中介者:建立工作群 (中介者),所有人只在群里沟通,员工只和群交互,无需关注其他员工,耦合度大幅降低;
- 类图:
- 实现代码:
// ------------- 抽象中介者 ------------- interface Mediator { // 核心方法:接收同事类的消息并转发 notify(colleague: Colleague, message: string): void; } // ------------- 抽象同事类 ------------- abstract class Colleague { protected mediator: Mediator; public name: string; constructor(mediator: Mediator, name: string) { this.mediator = mediator; this.name = name; } // 发送消息(统一通过中介) public send(message: string): void { console.log(`${this.name} 发送消息:${message}`); this.mediator.notify(this, message); } // 接收消息(由中介转发) public abstract receive(message: string): void; } // ------------- 具体同事类:租客 ------------- class Tenant extends Colleague { constructor(mediator: Mediator, name: string) { super(mediator, name); } public receive(message: string): void { console.log(`租客【${this.name}】收到消息:${message}`); } } // ------------- 具体同事类:房东 ------------- class Landlord extends Colleague { constructor(mediator: Mediator, name: string) { super(mediator, name); } public receive(message: string): void { console.log(`房东【${this.name}】收到消息:${message}`); } } // ------------- 具体同事类:保洁 ------------- class Cleaner extends Colleague { constructor(mediator: Mediator, name: string) { super(mediator, name); } public receive(message: string): void { console.log(`保洁【${this.name}】收到消息:${message}`); } } // ------------- 具体中介者:租房中介 ------------- class RentalMediator implements Mediator { // 维护所有同事类的引用 private tenant?: Tenant; private landlord?: Landlord; private cleaner?: Cleaner; // 设置租客 public setTenant(tenant: Tenant): void { this.tenant = tenant; } // 设置房东 public setLandlord(landlord: Landlord): void { this.landlord = landlord; } // 设置保洁 public setCleaner(cleaner: Cleaner): void { this.cleaner = cleaner; } // 核心逻辑:根据发送者转发消息给对应接收者 public notify(colleague: Colleague, message: string): void { if (colleague instanceof Tenant) { // 租客发消息 → 转发给房东 this.landlord?.receive(message); // 如果租客提了保洁需求,同时转发给保洁 if (message.includes("保洁")) { this.cleaner?.receive(`租客需要:${message}`); } } else if (colleague instanceof Landlord) { // 房东发消息 → 转发给租客 this.tenant?.receive(message); } else if (colleague instanceof Cleaner) { // 保洁发消息 → 转发给房东和租客 this.landlord?.receive(message); this.tenant?.receive(message); } } } // ------------- 测试代码(生动演示)------------- // 1. 创建中介 const rentalMediator = new RentalMediator(); // 2. 创建同事(租客、房东、保洁) const tenantZhang = new Tenant(rentalMediator, "张三"); const landlordLi = new Landlord(rentalMediator, "李阿姨"); const cleanerWang = new Cleaner(rentalMediator, "王师傅"); // 3. 中介绑定所有同事 rentalMediator.setTenant(tenantZhang); rentalMediator.setLandlord(landlordLi); rentalMediator.setCleaner(cleanerWang); // 4. 模拟交互 console.log("===== 场景1:租客问房租 ====="); tenantZhang.send("请问两室一厅的房租多少?"); console.log("\n===== 场景2:房东回复 ====="); landlordLi.send("每月3500,押一付三"); console.log("\n===== 场景3:租客提保洁需求 ====="); tenantZhang.send("入住前能帮忙做个全屋保洁吗?"); console.log("\n===== 场景4:保洁回复 ====="); cleanerWang.send("可以的,明天上午9点上门,费用150元");
常用使用场景
游戏 / 可视化中的元素交互
-
场景说明:Canvas 中有多个小球 (同事类),小球碰撞时改变颜色,通过中介者统一管理小球的绘制、移动、碰撞检测逻辑;
-
实现代码:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Canvas小球交互(中介者模式)</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #1a1a2e, #16213e, #0f3460); min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 20px; color: #fff; } .container { background: rgba(255, 255, 255, 0.05); border-radius: 16px; padding: 30px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.1); } h1 { text-align: center; margin-bottom: 20px; font-size: 28px; font-weight: 600; background: linear-gradient(90deg, #4cc9f0, #72efdd); -webkit-background-clip: text; background-clip: text; color: transparent; letter-spacing: 1px; } #gameCanvas { border-radius: 12px; background: rgba(10, 15, 30, 0.6); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2); cursor: pointer; transition: all 0.3s ease; } #gameCanvas:hover { box-shadow: 0 6px 25px rgba(76, 201, 240, 0.3); } .controls { margin-top: 20px; display: flex; gap: 15px; justify-content: center; flex-wrap: wrap; } button { padding: 10px 20px; border: none; border-radius: 8px; background: linear-gradient(90deg, #4361ee, #3a0ca3); color: white; font-size: 16px; font-weight: 500; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 4px 10px rgba(67, 97, 238, 0.2); } button:hover { transform: translateY(-2px); box-shadow: 0 6px 15px rgba(67, 97, 238, 0.4); background: linear-gradient(90deg, #3a0ca3, #4361ee); } button:active { transform: translateY(0); } .info { margin-top: 15px; text-align: center; font-size: 14px; color: rgba(255, 255, 255, 0.7); } </style> </head> <body> <div class="container"> <h1>Canvas 小球交互(中介者模式)</h1> <canvas id="gameCanvas" width="800" height="600"></canvas> <div class="controls"> <button id="addBall">添加小球</button> <button id="clearBalls">清空小球</button> <button id="resetBalls">重置小球</button> </div> <div class="info"> 小球碰撞时会交换速度并随机变色 | 初始化无重叠 | 点击按钮/画布可添加小球 </div> </div> <script> // 1. 定义小球类(同事类) class Ball { constructor(x, y, radius, color) { this.x = x; this.y = y; this.radius = radius; // 随机速度(优化速度范围,让运动更流畅) this.dx = (Math.random() - 0.5) * 4; this.dy = (Math.random() - 0.5) * 4; this.color = color; this.alpha = 0.9; // 透明度 } // 绘制小球(优化视觉效果:添加渐变和阴影) draw(ctx) { ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); // 创建径向渐变,让小球有3D效果 const gradient = ctx.createRadialGradient( this.x - this.radius/3, this.y - this.radius/3, 0, this.x, this.y, this.radius ); gradient.addColorStop(0, `rgba(255,255,255,${this.alpha})`); gradient.addColorStop(1, this.color); ctx.fillStyle = gradient; // 添加阴影效果 ctx.shadowColor = this.color; ctx.shadowBlur = 15; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; ctx.fill(); ctx.closePath(); // 重置阴影,避免影响其他绘制 ctx.shadowBlur = 0; } // 移动小球 move(canvasWidth, canvasHeight) { // 碰撞边界反弹(优化精度,避免小球超出边界) if (this.x + this.radius >= canvasWidth) { this.x = canvasWidth - this.radius; this.dx = -this.dx; } else if (this.x - this.radius <= 0) { this.x = this.radius; this.dx = -this.dx; } if (this.y + this.radius >= canvasHeight) { this.y = canvasHeight - this.radius; this.dy = -this.dy; } else if (this.y - this.radius <= 0) { this.y = this.radius; this.dy = -this.dy; } this.x += this.dx; this.y += this.dy; } // 改变颜色(优化颜色生成,更鲜艳) changeColor() { // 生成更鲜艳的随机颜色(固定高饱和度) const hue = Math.floor(Math.random() * 360); this.color = `hsla(${hue}, 80%, 60%, ${this.alpha})`; } } // 2. 定义游戏中介者 class GameMediator { constructor(canvasId) { this.canvas = document.getElementById(canvasId); this.ctx = this.canvas.getContext('2d'); this.canvasWidth = this.canvas.width; this.canvasHeight = this.canvas.height; this.balls = []; // 初始化小球数组 this.initBalls(10); // 初始化10个小球(已做碰撞检测) this.startGameLoop(); this.bindEvents(); // 绑定控制按钮事件 } // 检测单个小球是否与已有小球重叠 isOverlapping(newBall) { for (const ball of this.balls) { const dx = newBall.x - ball.x; const dy = newBall.y - ball.y; const distance = Math.sqrt(dx * dx + dy * dy); // 距离小于两球半径之和 → 重叠 if (distance < newBall.radius + ball.radius) { return true; } } return false; } // 生成不重叠的小球位置 generateNonOverlappingBall(radius) { let newBall; let attempts = 0; const maxAttempts = 1000; // 防止无限循环 // 循环生成位置,直到找到不重叠的位置(或达到最大尝试次数) do { const x = Math.random() * (this.canvasWidth - radius * 2) + radius; const y = Math.random() * (this.canvasHeight - radius * 2) + radius; const hue = Math.floor(Math.random() * 360); const color = `hsla(${hue}, 80%, 60%, 0.9)`; newBall = new Ball(x, y, radius, color); attempts++; } while (this.isOverlapping(newBall) && attempts < maxAttempts); return newBall; } // 初始化小球(核心优化:初始化时做碰撞检测,避免重叠) initBalls(count) { // 清空原有小球 this.balls = []; for (let i = 0; i < count; i++) { const radius = Math.random() * 15 + 10; // 调整半径范围,更协调 // 生成不重叠的小球 const ball = this.generateNonOverlappingBall(radius); this.balls.push(ball); } } // 添加单个小球(优化:新增小球也做碰撞检测) addBall() { const radius = Math.random() * 15 + 10; const ball = this.generateNonOverlappingBall(radius); this.balls.push(ball); } // 清空所有小球 clearBalls() { this.balls = []; } // 重置小球 resetBalls() { this.initBalls(10); // 复用初始化方法,自动做碰撞检测 } // 碰撞检测(中介者核心逻辑,优化性能:减少重复计算) checkCollision() { const ballCount = this.balls.length; // 只遍历 i < j 的组合,避免重复检测 for (let i = 0; i < ballCount; i++) { const ball1 = this.balls[i]; for (let j = i + 1; j < ballCount; j++) { const ball2 = this.balls[j]; const dx = ball1.x - ball2.x; const dy = ball1.y - ball2.y; const distanceSquared = dx * dx + dy * dy; // 优化:避免开平方,提升性能 const minDistance = ball1.radius + ball2.radius; // 距离平方小于最小距离平方 → 碰撞 if (distanceSquared < minDistance * minDistance) { // 交换速度(模拟物理反弹) [ball1.dx, ball2.dx] = [ball2.dx, ball1.dx]; [ball1.dy, ball2.dy] = [ball2.dy, ball1.dy]; // 轻微分离小球,避免持续重叠 const overlap = minDistance - Math.sqrt(distanceSquared); const separationX = (dx / Math.sqrt(distanceSquared)) * overlap / 2; const separationY = (dy / Math.sqrt(distanceSquared)) * overlap / 2; ball1.x += separationX; ball1.y += separationY; ball2.x -= separationX; ball2.y -= separationY; // 改变颜色 ball1.changeColor(); ball2.changeColor(); } } } } // 游戏主循环 startGameLoop() { const loop = () => { // 清空画布(带半透明效果,让运动轨迹更柔和) this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight); // 绘制背景渐变(增加层次感) const bgGradient = this.ctx.createLinearGradient(0, 0, this.canvasWidth, this.canvasHeight); bgGradient.addColorStop(0, 'rgba(10, 15, 30, 0.4)'); bgGradient.addColorStop(1, 'rgba(20, 30, 60, 0.4)'); this.ctx.fillStyle = bgGradient; this.ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight); // 移动所有小球 this.balls.forEach(ball => ball.move(this.canvasWidth, this.canvasHeight)); // 检测碰撞(运行时+初始化后都生效) this.checkCollision(); // 绘制所有小球 this.balls.forEach(ball => ball.draw(this.ctx)); requestAnimationFrame(loop); }; loop(); } // 绑定控制按钮事件 bindEvents() { document.getElementById('addBall').addEventListener('click', () => this.addBall()); document.getElementById('clearBalls').addEventListener('click', () => this.clearBalls()); document.getElementById('resetBalls').addEventListener('click', () => this.resetBalls()); // 点击画布添加小球(新增小球也做碰撞检测) this.canvas.addEventListener('click', (e) => { const rect = this.canvas.getBoundingClientRect(); const radius = Math.random() * 15 + 10; // 先基于点击位置生成小球 let newBall = new Ball( e.clientX - rect.left, e.clientY - rect.top, radius, `hsla(${Math.floor(Math.random() * 360)}, 80%, 60%, 0.9)` ); // 如果重叠,重新生成位置 if (this.isOverlapping(newBall)) { newBall = this.generateNonOverlappingBall(radius); } this.balls.push(newBall); }); } } // 3. 初始化游戏中介者 new GameMediator('gameCanvas'); </script> </body> </html> -
效果演示:
购物车多组件联动(电商最常用)
-
场景:商品选择、数量增减、优惠券、总价计算 互相影响,用中介者统一管理;
-
实现代码:
// 中介者:购物车中控 class CartMediator { private items = new Map<string, { price: number; count: number }>() private coupon = 0 addItem(id: string, price: number) { this.items.set(id, { price, count: 1 }) this.updateTotal() } changeCount(id: string, count: number) { const item = this.items.get(id) if (item) item.count = count this.updateTotal() } setCoupon(coupon: number) { this.coupon = coupon this.updateTotal() } updateTotal() { let total = 0 for (const { price, count } of this.items.values()) { total += price * count } total = Math.max(0, total - this.coupon) console.log('💰 最终总价:', total) } } // 组件只跟中介者通信,不互相调用 const mediator = new CartMediator() // 商品组件 mediator.addItem('item1', 100) // 数量组件 mediator.changeCount('item1', 2) // 优惠券组件 mediator.setCoupon(50)
全局弹窗管理器(全局唯一弹窗)
-
场景:同时只能弹一个弹窗,后面的自动排队或覆盖;
-
实现代码:
class ModalMediator { private currentModal: string | null = null open(modalName: string) { if (this.currentModal) { console.log(`关闭:${this.currentModal}`) } this.currentModal = modalName console.log(`打开:${modalName}`) } close() { console.log(`关闭:${this.currentModal}`) this.currentModal = null } } const modal = new ModalMediator() modal.open('登录弹窗') modal.open('支付弹窗') // 自动关闭上一个
实时聊天房间(多人 IM 房间)
-
场景:房间内所有人的消息都经过中介者转发,不直接点对点;
-
实现代码:
class RoomMediator { private users = new Map<string, (msg: string) => void>() join(userId: string, receiver: (msg: string) => void) { this.users.set(userId, receiver) } send(from: string, content: string) { const msg = `【${from}】:${content}` this.users.forEach((cb) => cb(msg)) } } const room = new RoomMediator() room.join('userA', (msg) => console.log('A收到:', msg)) room.join('userB', (msg) => console.log('B收到:', msg)) room.send('userA', '大家好')
中介者模式的优缺点
-
优点:
- 降低耦合度:对象间不再直接引用,通过中介者间接通信;
- 集中控制:交互逻辑集中在中介者中,便于维护;
- 简化对象协议:用一对多替代多对多的交互关系;
- 提高可复用性:独立的对象更容易复用;
-
缺点:
- 中介者可能变得复杂:随着交互逻辑增加,中介者可能变成"上帝对象";
- 性能问题:所有通信都要经过中介者,可能成为性能瓶颈;
- 过度集中:过多的逻辑集中在中介者中,违反了单一职责原则;
何时使用中介者模式?
-
当对象间的交互复杂且多变时;
-
当想复用对象而不关心其协作对象时;
-
当需要集中控制对象间的交互时;
-
当系统中有多个对象需要松耦合协作时;
与其他模式的关系
-
观察者模式:中介者可以使用观察者模式来实现通知机制;
-
外观模式:外观模式提供统一的接口,而中介模式协调对象间的通信;
-
命令模式:可以用命令对象封装请求,通过中介者转发;
备忘录模式(行为型模式)
上一篇