中介者模式核心解析

  1. 中介者模式 (Mediator Pattern)中介者 = 中控室 / 调度中心,所有组件只跟中介说话,不互相说话
    1. 核心是引入一个 “中介者” 对象,让原本互相耦合、直接通信的多个对象 (同事类),改为通过中介者间接通信;
    2. 可以类比生活场景:
      • 无中介者:公司里每个员工直接私聊沟通工作,10 个员工需要建立 45 组私聊 (N*(N-1)/2),一人离职要通知所有人;
      • 有中介者:建立工作群 (中介者),所有人只在群里沟通,员工只和群交互,无需关注其他员工,耦合度大幅降低;
  2. 类图:
  3. 实现代码:
    // ------------- 抽象中介者 -------------
    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元");
    

常用使用场景

游戏 / 可视化中的元素交互

  1. 场景说明:Canvas 中有多个小球 (同事类),小球碰撞时改变颜色,通过中介者统一管理小球的绘制、移动、碰撞检测逻辑;

  2. 实现代码:

    <!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>
    
  3. 效果演示:

购物车多组件联动(电商最常用)

  1. 场景:商品选择、数量增减、优惠券、总价计算 互相影响,用中介者统一管理;

  2. 实现代码:

    // 中介者:购物车中控
    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)
    

全局弹窗管理器(全局唯一弹窗)

  1. 场景:同时只能弹一个弹窗,后面的自动排队或覆盖;

  2. 实现代码:

    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 房间)

  1. 场景:房间内所有人的消息都经过中介者转发,不直接点对点;

  2. 实现代码:

    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', '大家好')
    

中介者模式的优缺点

  1. 优点:

    1. 降低耦合度:对象间不再直接引用,通过中介者间接通信;
    2. 集中控制:交互逻辑集中在中介者中,便于维护;
    3. 简化对象协议:用一对多替代多对多的交互关系;
    4. 提高可复用性:独立的对象更容易复用;
  2. 缺点:

    1. 中介者可能变得复杂:随着交互逻辑增加,中介者可能变成"上帝对象";
    2. 性能问题:所有通信都要经过中介者,可能成为性能瓶颈;
    3. 过度集中:过多的逻辑集中在中介者中,违反了单一职责原则;

何时使用中介者模式?

  1. 当对象间的交互复杂且多变时;

  2. 当想复用对象而不关心其协作对象时;

  3. 当需要集中控制对象间的交互时;

  4. 当系统中有多个对象需要松耦合协作时;

与其他模式的关系

  1. 观察者模式:中介者可以使用观察者模式来实现通知机制;

  2. 外观模式:外观模式提供统一的接口,而中介模式协调对象间的通信;

  3. 命令模式:可以用命令对象封装请求,通过中介者转发;

打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

你好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 中介者模式核心解析
  2. 2. 常用使用场景
    1. 2.1. 游戏 / 可视化中的元素交互
    2. 2.2. 购物车多组件联动(电商最常用)
    3. 2.3. 全局弹窗管理器(全局唯一弹窗)
    4. 2.4. 实时聊天房间(多人 IM 房间)
  3. 3. 中介者模式的优缺点
  4. 4. 何时使用中介者模式?
  5. 5. 与其他模式的关系