外观模式核心解析
- 外观模式:是一种结构型设计模式,核心思想是为复杂的子系统提供一个统一的 “门面” (外观类),外部调用者无需了解子系统内部的复杂逻辑,只需通过这个门面类即可完成对多个子系统的调用,降低系统耦合度、简化调用流程;
- 类图:
- 子系统类 (OrderService/PaymentService/NotificationService):各自负责单一功能,是复杂逻辑的具体实现者;
- 外观类 (PaymentFacade):
- 内部持有所有子系统的实例 (依赖);
- 对外暴露简化的 pay 方法,内部协调调用子系统的方法;
- 外部调用者只需和 PaymentFacade 交互,无需接触子系统。
- 实现代码:
// 子系统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 调用
-
前端经常需要调用多个接口完成一个业务 (如提交表单前先校验、再提交、最后查询结果),用外观模式封装这些接口调用,对外提供一个简单方法;
-
实现代码:
// 子系统:各个接口调用 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));
封装第三方库 / 工具的复杂配置
-
前端常用的第三方库 (如 echarts、高德地图、富文本编辑器) 初始化和配置逻辑复杂,用外观模式封装后,对外提供简单的初始化 / 调用方法;
-
实现代码:
// 外观类:简化 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 }]);
封装跨端 / 兼容逻辑
-
前端需要兼容不同浏览器、不同端 (H5 / 小程序 / APP) 的 API 时,用外观模式封装兼容逻辑,对外提供统一接口;
-
实现代码:
// 外观类:统一存储接口(兼容 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 操作 / 动画
-
多个 DOM 操作、动画组合成一个业务动作时 (如弹窗的 “打开” 包含:显示容器、添加动画、绑定事件、加载数据),用外观模式封装;
-
实现代码:
// 外观类:弹窗门面(封装所有复杂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);
外观模式优缺点
优点
-
简化接口:隐藏系统的复杂性,提供更简单的接口;
-
解耦:客户端与子系统解耦,降低耦合度;
-
提高安全性:隐藏子系统内部的实现细节;
-
提高灵活性:可以在不影响客户端的情况下修改子系统;
缺点
-
可能成为"上帝对象":如果设计不当,外观类可能变得过于庞大;
-
增加间接层:增加了一个新的层次,可能影响性能;
-
限制灵活性:可能限制高级用户对底层功能的访问;
适用场景
-
需要简化复杂系统的访问:如复杂的库、框架或 API;
-
需要解耦客户端和子系统:降低系统的耦合度;
-
需要分层系统:为每个子系统定义入口点;
-
需要封装遗留系统:为遗留代码提供新的接口;
代理模式(结构型模式)
上一篇