外观模式核心解析

  1. 外观模式:是一种结构型设计模式,核心思想是为复杂的子系统提供一个统一的 “门面” (外观类),外部调用者无需了解子系统内部的复杂逻辑,只需通过这个门面类即可完成对多个子系统的调用,降低系统耦合度、简化调用流程;
  2. 类图:
    1. 子系统类 (OrderService/PaymentService/NotificationService):各自负责单一功能,是复杂逻辑的具体实现者;
    2. 外观类 (PaymentFacade)
      • 内部持有所有子系统的实例 (依赖)
      • 对外暴露简化的 pay 方法,内部协调调用子系统的方法;
      • 外部调用者只需和 PaymentFacade 交互,无需接触子系统。
  3. 实现代码:
    // 子系统1:订单校验服务(对应类图中的 OrderService)
    class OrderService {
      /**
       * 校验订单是否有效
      * @param orderId 订单ID
      * @returns 校验结果
      */
      verifyOrder(orderId: string): boolean {
        console.log(`[订单服务] 校验订单 ${orderId} 有效性`);
        // 模拟校验逻辑:非空即有效
        return !!orderId;
      }
    }
    
    // 子系统2:支付处理服务(对应类图中的 PaymentService)
    class PaymentService {
      /**
       * 处理支付逻辑
      * @param orderId 订单ID
      * @param amount 支付金额
      * @returns 支付结果
      */
      processPayment(orderId: string, amount: number): boolean {
        console.log(`[支付服务] 处理订单 ${orderId} 支付,金额:${amount} 元`);
        // 模拟支付逻辑:金额大于0即成功
        return amount > 0;
      }
    }
    
    // 子系统3:消息通知服务(对应类图中的 NotificationService)
    class NotificationService {
      /**
       * 发送支付成功消息
      * @param orderId 订单ID
      */
      sendPaySuccessMsg(orderId: string): void {
        console.log(`[通知服务] 向用户发送订单 ${orderId} 支付成功的短信/推送`);
      }
    }
    
    // 外观类:支付门面(对应类图中的 PaymentFacade)
    class PaymentFacade {
      // 内部持有所有子系统实例
      private orderService: OrderService;
      private paymentService: PaymentService;
      private notificationService: NotificationService;
    
      constructor() {
        // 初始化子系统实例
        this.orderService = new OrderService();
        this.paymentService = new PaymentService();
        this.notificationService = new NotificationService();
      }
    
      /**
       * 对外暴露的统一支付方法(门面接口)
      * @param orderId 订单ID
      * @param amount 支付金额
      * @returns 整体支付结果
      */
      pay(orderId: string, amount: number): boolean {
        try {
          // 1. 调用子系统1:校验订单
          const isOrderValid = this.orderService.verifyOrder(orderId);
          if (!isOrderValid) {
            throw new Error(`订单 ${orderId} 无效`);
          }
    
          // 2. 调用子系统2:处理支付
          const isPaySuccess = this.paymentService.processPayment(orderId, amount);
          if (!isPaySuccess) {
            throw new Error(`订单 ${orderId} 支付失败`);
          }
    
          // 3. 调用子系统3:发送通知
          this.notificationService.sendPaySuccessMsg(orderId);
    
          console.log(`[支付门面] 订单 ${orderId} 支付流程全部完成`);
          return true;
        } catch (error) {
          console.error(`[支付门面] 支付失败:${(error as Error).message}`);
          return false;
        }
      }
    }
    
    // -------------- 测试代码(外部调用者)--------------
    // 调用者只需创建门面类实例,调用统一的 pay 方法
    const paymentFacade = new PaymentFacade();
    // 测试1:正常支付
    const payResult1 = paymentFacade.pay("ORDER_123456", 99);
    console.log("支付结果1:", payResult1); // true
    
    console.log("--- 分割线 ---");
    
    // 测试2:订单无效(空订单ID)
    const payResult2 = paymentFacade.pay("", 99);
    console.log("支付结果2:", payResult2); // false
    

常用使用场景

封装复杂的 API 调用

  1. 前端经常需要调用多个接口完成一个业务 (如提交表单前先校验、再提交、最后查询结果),用外观模式封装这些接口调用,对外提供一个简单方法;

  2. 实现代码:

    // 子系统:各个接口调用
    class ApiService {
      checkForm(data: any) { 
        return fetch('/api/check', { method: 'POST', body: data }); 
      }
      submitForm(data: any) { 
        return fetch('/api/submit', { method: 'POST', body: data }); 
      }
      getResult(id: string) { 
        return fetch(`/api/result/${id}`); 
      }
    }
    
    // 外观类:封装表单提交逻辑
    class FormSubmitFacade {
      private apiService: ApiService;
      constructor() { this.apiService = new ApiService(); }
      
      async submit(data: any) {
        const checkRes = await this.apiService.checkForm(data);
        if (!checkRes.ok) throw new Error('表单校验失败');
        
        const submitRes = await this.apiService.submitForm(data);
        const submitData = await submitRes.json();
        
        const resultRes = await this.apiService.getResult(submitData.id);
        return resultRes.json();
      }
    }
    
    // 调用者只需一行代码
    const facade = new FormSubmitFacade();
    facade.submit({ name: '张三' }).then(res => console.log(res));
    

封装第三方库 / 工具的复杂配置

  1. 前端常用的第三方库 (如 echarts、高德地图、富文本编辑器) 初始化和配置逻辑复杂,用外观模式封装后,对外提供简单的初始化 / 调用方法;

  2. 实现代码:

    // 外观类:简化 ECharts 初始化
    class EChartsFacade {
      init(container: HTMLElement, type: 'line' | 'bar', data: any[]) {
        // 内部封装复杂的配置逻辑
        const chart = echarts.init(container);
        const option = {
          xAxis: { type: 'category', data: data.map(item => item.name) },
          yAxis: { type: 'value' },
          series: [{ type, data: data.map(item => item.value) }]
        };
        chart.setOption(option);
        return chart;
      }
    }
    
    // 调用者使用
    const facade = new EChartsFacade();
    facade.init(document.getElementById('chart'), 'line', [{ name: '1月', value: 100 }]);
    

封装跨端 / 兼容逻辑

  1. 前端需要兼容不同浏览器、不同端 (H5 / 小程序 / APP)API 时,用外观模式封装兼容逻辑,对外提供统一接口;

  2. 实现代码:

    // 外观类:统一存储接口(兼容 localStorage/小程序 wx.setStorage)
    class StorageFacade {
      setItem(key: string, value: any) {
        if (typeof wx !== 'undefined') {
          // 小程序环境
          wx.setStorageSync(key, value);
        } else {
          // H5 环境
          localStorage.setItem(key, JSON.stringify(value));
        }
      }
      
      getItem(key: string) {
        if (typeof wx !== 'undefined') {
          return wx.getStorageSync(key);
        } else {
          return JSON.parse(localStorage.getItem(key) || 'null');
        }
      }
    }
    
    // 调用者无需关心环境,直接调用
    const storage = new StorageFacade();
    storage.setItem('user', { name: '张三' });
    console.log(storage.getItem('user'));
    

封装复杂的 DOM 操作 / 动画

  1. 多个 DOM 操作、动画组合成一个业务动作时 (如弹窗的 “打开” 包含:显示容器、添加动画、绑定事件、加载数据),用外观模式封装;

  2. 实现代码:

    // 外观类:弹窗门面(封装所有复杂DOM/动画操作)
    class ToastFacade {
      // 私有属性:存储弹窗DOM节点
      private toastElement: HTMLDivElement | null = null;
      private maskElement: HTMLDivElement | null = null;
    
      /**
      * 对外暴露:打开弹窗(核心门面方法)
      * @param message 弹窗提示文本
      */
      open(message: string): void {
        // 1. 创建DOM结构
        this.createDom(message);
        // 2. 设置样式
        this.setStyle();
        // 3. 绑定事件
        this.bindEvents();
        // 4. 执行显示动画
        this.showAnimation();
      }
    
      /**
      * 对外暴露:关闭弹窗(核心门面方法)
      */
      close(): void {
        if (!this.toastElement || !this.maskElement) return;
        
        // 1. 执行隐藏动画
        this.hideAnimation();
        // 2. 动画结束后清理DOM
        setTimeout(() => {
          this.cleanDom();
        }, 300); // 与动画时长一致
      }
    
      // 子系统1:创建弹窗DOM(私有方法,外部不可见)
      private createDom(message: string): void {
        // 创建遮罩层
        this.maskElement = document.createElement('div');
        this.maskElement.className = 'toast-mask';
        
        // 创建弹窗容器
        this.toastElement = document.createElement('div');
        this.toastElement.className = 'toast-container';
        this.toastElement.innerHTML = `
          <div class="toast-content">${message}</div>
          <button class="toast-close">×</button>
        `;
        
        // 追加到页面
        this.maskElement.appendChild(this.toastElement);
        document.body.appendChild(this.maskElement);
      }
    
      // 子系统2:设置弹窗样式(私有方法)
      private setStyle(): void {
        if (!this.toastElement || !this.maskElement) return;
    
        // 遮罩层样式
        this.maskElement.style.cssText = `
          position: fixed; top: 0; left: 0; width: 100%; height: 100%;
          background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center;
          opacity: 0; transition: opacity 0.3s ease;
        `;
    
        // 弹窗容器样式
        this.toastElement.style.cssText = `
          width: 280px; padding: 20px; background: #fff; border-radius: 8px;
          position: relative; transform: scale(0.8); transition: transform 0.3s ease;
        `;
    
        // 关闭按钮样式
        const closeBtn = this.toastElement.querySelector('.toast-close');
        if (closeBtn) {
          closeBtn.style.cssText = `
            position: absolute; top: 10px; right: 10px; border: none; background: none;
            font-size: 18px; cursor: pointer; color: #999;
          `;
        }
      }
    
      // 子系统3:绑定事件(私有方法)
      private bindEvents(): void {
        // 关闭按钮点击事件
        const closeBtn = this.toastElement?.querySelector('.toast-close');
        closeBtn?.addEventListener('click', () => this.close());
    
        // 遮罩层点击事件
        this.maskElement?.addEventListener('click', (e) => {
          if (e.target === this.maskElement) this.close();
        });
      }
    
      // 子系统4:显示动画(私有方法)
      private showAnimation(): void {
        if (!this.toastElement || !this.maskElement) return;
        // 触发动画
        setTimeout(() => {
          this.maskElement!.style.opacity = '1';
          this.toastElement!.style.transform = 'scale(1)';
        }, 0);
      }
    
      // 子系统5:隐藏动画(私有方法)
      private hideAnimation(): void {
        if (!this.toastElement || !this.maskElement) return;
        this.maskElement.style.opacity = '0';
        this.toastElement.style.transform = 'scale(0.8)';
      }
    
      // 子系统6:清理DOM(私有方法)
      private cleanDom(): void {
        if (this.maskElement) {
          document.body.removeChild(this.maskElement);
          this.toastElement = null;
          this.maskElement = null;
        }
      }
    }
    
    // -------------- 测试调用(外部使用)--------------
    // 1. 创建门面实例
    const toast = new ToastFacade();
    
    // 2. 点击按钮打开弹窗(仅需调用open方法)
    document.addEventListener('click', () => {
      toast.open('这是一个外观模式封装的弹窗提示!');
    });
    
    // 3. 也可主动关闭(比如3秒后自动关闭)
    // setTimeout(() => toast.close(), 3000);
    

外观模式优缺点

优点

  1. 简化接口:隐藏系统的复杂性,提供更简单的接口;

  2. 解耦:客户端与子系统解耦,降低耦合度;

  3. 提高安全性:隐藏子系统内部的实现细节;

  4. 提高灵活性:可以在不影响客户端的情况下修改子系统;

缺点

  1. 可能成为"上帝对象":如果设计不当,外观类可能变得过于庞大;

  2. 增加间接层:增加了一个新的层次,可能影响性能;

  3. 限制灵活性:可能限制高级用户对底层功能的访问;

适用场景

  1. 需要简化复杂系统的访问:如复杂的库、框架或 API

  2. 需要解耦客户端和子系统:降低系统的耦合度;

  3. 需要分层系统:为每个子系统定义入口点;

  4. 需要封装遗留系统:为遗留代码提供新的接口;

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

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

粽子

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

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

了解更多

目录

  1. 1. 外观模式核心解析
  2. 2. 常用使用场景
    1. 2.1. 封装复杂的 API 调用
    2. 2.2. 封装第三方库 / 工具的复杂配置
    3. 2.3. 封装跨端 / 兼容逻辑
    4. 2.4. 封装复杂的 DOM 操作 / 动画
  3. 3. 外观模式优缺点
    1. 3.1. 优点
    2. 3.2. 缺点
  4. 4. 适用场景