当前位置: 首页 > news >正文

JavaScript设计模式 -- 外观模式

在实际开发中,往往会遇到多个子系统协同工作时,直接操作各个子系统不仅接口繁琐,还容易导致客户端与内部实现紧密耦合。**外观模式(Facade Pattern)**通过为多个子系统提供一个统一的高层接口,将复杂性隐藏在内部,从而降低耦合,提高代码的可维护性与易用性。

本文将详细介绍外观模式的基本概念、结构和优缺点,并通过多个示例展示如何在不同场景下运用外观模式。

外观模式简介

外观模式属于结构型设计模式,其核心思想是为多个复杂子系统提供一个统一的接口(外观),从而让客户端无需了解内部实现细节即可调用系统功能。外观模式不仅能简化调用流程,还能使系统的内部变化对客户端透明,便于后续扩展和维护。


外观模式的结构与特点

主要角色:

  • 外观(Facade)
    对外提供一个统一的接口,封装多个子系统的功能调用。

  • 子系统(Subsystem)
    完成具体的业务逻辑,各自拥有独立的接口和实现。

  • 客户端(Client)
    只需通过外观接口与系统交互,无需关心子系统内部的复杂细节。

特点:

  • 简化接口:将复杂的操作组合成简单的方法调用。
  • 降低耦合:客户端只依赖外观接口,而不直接与各个子系统耦合。
  • 隐藏复杂性:将系统内部的实现细节封装在外观类中,对外部屏蔽。

多方面示例详解

下面通过多个示例,展示如何利用外观模式解决不同场景下的复杂性问题。

示例 1:家庭影院系统

在家庭影院中,通常需要协调音响、投影仪、灯光、蓝光播放器等多个设备。直接控制这些设备非常繁琐,使用外观模式可以为家庭影院提供一键启动和关闭的简单接口。

// 子系统:音响
class Amplifier {on() {console.log('音响开启');}off() {console.log('音响关闭');}setVolume(level) {console.log(`音响音量设置为 ${level}`);}
}// 子系统:投影仪
class Projector {on() {console.log('投影仪开启');}off() {console.log('投影仪关闭');}setInput(input) {console.log(`投影仪输入源设置为 ${input}`);}
}// 子系统:灯光
class TheaterLights {dim(level) {console.log(`灯光调暗到 ${level}%`);}on() {console.log('灯光开启');}
}// 子系统:蓝光播放器
class BluRayPlayer {on() {console.log('蓝光播放器开启');}off() {console.log('蓝光播放器关闭');}play(movie) {console.log(`正在播放电影:《${movie}》`);}
}// 外观类:家庭影院外观
class HomeTheaterFacade {constructor(amp, projector, lights, bluRay) {this.amp = amp;this.projector = projector;this.lights = lights;this.bluRay = bluRay;}watchMovie(movie) {console.log('准备观看电影...');this.lights.dim(10);this.projector.on();this.projector.setInput('蓝光播放器');this.amp.on();this.amp.setVolume(5);this.bluRay.on();this.bluRay.play(movie);}endMovie() {console.log('结束观看电影...');this.lights.on();this.projector.off();this.amp.off();this.bluRay.off();}
}// 客户端调用
const amplifier = new Amplifier();
const projector = new Projector();
const lights = new TheaterLights();
const bluRayPlayer = new BluRayPlayer();const homeTheater = new HomeTheaterFacade(amplifier, projector, lights, bluRayPlayer);
homeTheater.watchMovie('阿凡达');
homeTheater.endMovie();

示例 2:电子商务系统统一接口

在电子商务平台中,下单流程可能涉及订单创建、支付处理、物流安排和通知发送等多个子系统。使用外观模式,可以将这些流程封装为一个简单的 placeOrder 接口。

// 子系统:订单处理
class OrderService {createOrder(orderDetails) {console.log(`订单已创建:${JSON.stringify(orderDetails)}`);return 'ORDER123';}
}// 子系统:支付系统
class PaymentService {processPayment(orderId, amount) {console.log(`订单 ${orderId} 支付金额 ${amount} 元成功`);}
}// 子系统:物流系统
class ShippingService {arrangeShipping(orderId) {console.log(`订单 ${orderId} 发货成功`);}
}// 子系统:通知系统
class NotificationService {sendNotification(message) {console.log(`发送通知:${message}`);}
}// 外观类:购物流程外观
class ShoppingFacade {constructor(orderService, paymentService, shippingService, notificationService) {this.orderService = orderService;this.paymentService = paymentService;this.shippingService = shippingService;this.notificationService = notificationService;}placeOrder(orderDetails, amount) {console.log('开始下单流程...');const orderId = this.orderService.createOrder(orderDetails);this.paymentService.processPayment(orderId, amount);this.shippingService.arrangeShipping(orderId);this.notificationService.sendNotification(`订单 ${orderId} 已完成处理`);}
}// 客户端调用
const orderService = new OrderService();
const paymentService = new PaymentService();
const shippingService = new ShippingService();
const notificationService = new NotificationService();const shopping = new ShoppingFacade(orderService, paymentService, shippingService, notificationService);
shopping.placeOrder({ item: '笔记本电脑', quantity: 1 }, 8000);

示例 3:网络请求聚合接口

在一个大型 Web 应用中,客户端可能需要从多个 API 接口获取数据(例如用户信息、订单信息、统计数据等)。通过外观模式,可以设计一个统一的 API 聚合层,对外暴露简洁的接口,而内部调用各个子 API。

// 子系统:用户服务
class UserService {fetchUser(userId) {console.log(`获取用户 ${userId} 信息...`);return { id: userId, name: '张三' };}
}// 子系统:订单服务
class OrderServiceAPI {fetchOrders(userId) {console.log(`获取用户 ${userId} 的订单...`);return [{ orderId: 'ORDER123', item: '手机' }];}
}// 子系统:统计服务
class StatsService {fetchStats(userId) {console.log(`获取用户 ${userId} 的统计数据...`);return { totalOrders: 5, totalSpent: 1200 };}
}// 外观类:API 聚合器
class APIServiceFacade {constructor(userService, orderService, statsService) {this.userService = userService;this.orderService = orderService;this.statsService = statsService;}getUserDashboard(userId) {const user = this.userService.fetchUser(userId);const orders = this.orderService.fetchOrders(userId);const stats = this.statsService.fetchStats(userId);return {user,orders,stats};}
}// 客户端调用
const userServiceInstance = new UserService();
const orderServiceInstance = new OrderServiceAPI();
const statsServiceInstance = new StatsService();const apiFacade = new APIServiceFacade(userServiceInstance, orderServiceInstance, statsServiceInstance);
const dashboard = apiFacade.getUserDashboard(101);
console.log('用户仪表盘数据:', dashboard);

示例 4:游戏初始化外观

在游戏开发中,启动一个游戏往往需要初始化多个子系统,如图形引擎、音频系统、输入管理等。通过外观模式,可以为游戏引擎提供一个统一的初始化接口,简化启动流程。

// 子系统:图形引擎
class GraphicsEngine {init() {console.log('图形引擎初始化完成');}
}// 子系统:音频系统
class AudioSystem {init() {console.log('音频系统初始化完成');}
}// 子系统:输入管理
class InputManager {init() {console.log('输入管理初始化完成');}
}// 外观类:游戏引擎外观
class GameEngineFacade {constructor(graphics, audio, input) {this.graphics = graphics;this.audio = audio;this.input = input;}initializeGame() {console.log('游戏启动中...');this.graphics.init();this.audio.init();this.input.init();console.log('游戏初始化完毕');}
}// 客户端调用
const graphicsEngine = new GraphicsEngine();
const audioSystem = new AudioSystem();
const inputManager = new InputManager();const gameEngine = new GameEngineFacade(graphicsEngine, audioSystem, inputManager);
gameEngine.initializeGame();

示例 5:跨平台资源加载

在移动开发或跨平台项目中,不同平台可能需要加载不同的资源或配置。利用外观模式可以创建一个资源加载外观,根据当前平台选择合适的加载器,从而对外提供统一的加载接口。

// 子系统:Android 资源加载器
class AndroidResourceLoader {loadResources() {console.log('加载 Android 平台资源...');}
}// 子系统:iOS 资源加载器
class IOSResourceLoader {loadResources() {console.log('加载 iOS 平台资源...');}
}// 外观类:跨平台资源加载器
class ResourceFacade {constructor(platform) {// 假设 platform 值为 'android' 或 'ios'this.loader = platform === 'android'? new AndroidResourceLoader(): new IOSResourceLoader();}load() {this.loader.loadResources();}
}// 客户端调用
const currentPlatform = 'ios'; // 模拟当前平台为 iOS
const resourceLoader = new ResourceFacade(currentPlatform);
resourceLoader.load();

外观模式的优缺点

优点

  • 简化接口:将多个子系统的调用封装成一个简单接口,降低使用复杂度。
  • 降低耦合:客户端与各子系统解耦,任何子系统的变化只需在外观层做适配。
  • 隐藏内部复杂性:外观模式屏蔽了子系统实现细节,使得系统更易于使用和维护。

缺点

  • 灵活性降低:外观模式封装了子系统的所有功能,可能限制了对某些细节的直接控制。
  • 外观类可能过于庞大:当涉及的子系统很多时,外观类的职责可能变得过于繁杂,需要合理设计职责分离。

总结

外观模式通过为复杂系统提供一个统一而简洁的接口,有效降低了客户端与各子系统之间的耦合,使得系统调用更加直观和易于维护。本文通过家庭影院、电子商务、网络请求聚合、游戏初始化和跨平台资源加载五个示例,展示了外观模式在不同场景下的应用。希望这些实例能够帮助你在实际项目中发现并利用外观模式带来的简化接口和隐藏复杂性的优势。

欢迎在评论区分享你的使用心得或疑问!

相关文章:

JavaScript设计模式 -- 外观模式

在实际开发中,往往会遇到多个子系统协同工作时,直接操作各个子系统不仅接口繁琐,还容易导致客户端与内部实现紧密耦合。**外观模式(Facade Pattern)**通过为多个子系统提供一个统一的高层接口,将复杂性隐藏…...

百达翡丽(Patek Philippe):瑞士制表的巅峰之作(中英双语)

百达翡丽(Patek Philippe):瑞士制表的巅峰之作 在钟表界,百达翡丽(Patek Philippe) 一直被誉为“世界三大名表”之一,并且常被认为是其中的至高存在。一句“没人能真正拥有一枚百达翡丽&#x…...

阿里云一键部署DeepSeek-V3、DeepSeek-R1模型

目录 支持的模型列表 模型部署 模型调用 WebUI使用 在线调试 API调用 关于成本 FAQ 点击部署后服务长时间等待 服务部署成功后,调用API返回404 请求太长导致EAS网关超时 部署完成后,如何在EAS的在线调试页面调试 模型部署之后没有“联网搜索…...

分享一款AI绘画图片展示和分享的小程序

🎨奇绘图册 【开源】一款帮AI绘画爱好者维护绘图作品的小程序 查看Demo 反馈 github 文章目录 前言一、奇绘图册是什么?二、项目全景三、预览体验3.1 截图示例3.2 在线体验 四、功能介绍4.1 小程序4.2 服务端 五、安装部署5.1 快速开始~~5.2 手动部…...

【练习】【双指针】力扣热题100 283. 移动零

题目 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 请注意 ,必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0] 示例 2: 输入: nums [0] 输出…...

QT 互斥锁

一、概述 1、在多线程编程中,为了防止多个线程同时访问共享资源而导致的不确定性和错误,经常会使用互斥锁(Mutex)进行保护。 2、QMutex是Qt提供的一个互斥锁类,用于确保在同一时间只有一个线程访问共享资源。 3、QM…...

什么是算法的空间复杂度和时间复杂度,分别怎么衡量。

1. 时间复杂度 时间复杂度衡量的是算法运行时间与输入规模之间的关系。它通常用大O记号(Big O Notation)表示,例如 O(1)、O(n)、O(n2) 等。 衡量方法: 常数时间复杂度 O(1):无论输入规模如何,算法的执行时…...

VMware Workstation 17.0 Pro创建虚拟机并安装Ubuntu22.04与ubuntu20.04(双版本同时存在)《包含小问题总结》

目录 一、创建虚拟机 二、下载安装22.04 三、一些配置问题总结(小屏,网络,复制贴贴等) 1、网络问题 2、sudo apt install net-tools出现无法定为软件包 3、小屏与ubuntu虚拟机与windows系统之间复制粘贴 4、安装终端:Termi…...

Windows 10 ARM工控主板CAN总线实时性能测试

在常规的Windows系统中支持CAN总线应用,需要外接CAN总线适配器,通常为USB转CAN模块或PCI接口CAN卡。实时性本身是CAN总线的显著特性之一,但由于Windows并非实时操作系统,应用程序容易受到系统CPU负载影响,导致调度周期…...

如何在不依赖函数调用功能的情况下结合工具与大型语言模型

当大型语言模型(LLM)原生不支持函数调用功能时,如何实现智能工具调度?本文通过自然语言解析结构化输出控制的方法来实现。 GitHub代码地址 核心实现步骤 定义工具函数 使用tool装饰器声明可调用工具: from langcha…...

【Linux AnolisOS】关于Docker的一系列问题。尤其是拉取东西时的网络问题,镜像源问题。

AnolisOS 8中使用Docker部署(全)_anolis安装docker-CSDN博客 从在虚拟机安装龙蜥到安装docker上面这篇文章写的很清晰了,我重点讲述我解决文章里面问题一些的方法。 问题1: docker: Get https://registry-1.docker.io/v2/: net/h…...

【Elasticsearch】Mapping概述

以下是Elasticsearch中提到的关于Mapping的各模块概述: --- 1.Dynamic mapping(动态映射) 动态映射是指Elasticsearch在索引文档时,自动检测字段类型并创建字段映射的过程。当你首次索引一个文档时,Elasticsearch会根…...

GPT-4o悄然升级:能力与个性双突破,AI竞技场再掀波澜

在大模型竞技场中,GPT-4o悄悄发布了全新版本,凭借其卓越的多项能力,迅速超越了DeepSeek-R1,成功登上并列第一的位置。这次更新不仅在数学(第6名)上有所突破,还在创意写作、编程、指令遵循、长文…...

如何选择合适的超参数来训练Bert和TextCNN模型?

选择合适的超参数来训练Bert和TextCNN模型是一个复杂但关键的过程,它会显著影响模型的性能。以下是一些常见的超参数以及选择它们的方法: 1. 与数据处理相关的超参数 最大序列长度(max_length) 含义:指输入到Bert模…...

C# SpinLock 类 使用详解

总目录 前言 SpinLock 是 C# 中一种轻量级的自旋锁,属于 System.Threading 命名空间,专为极短时间锁竞争的高性能场景设计。它通过忙等待(自旋)而非阻塞线程来减少上下文切换开销,适用于锁持有时间极短(如…...

【linux】在 Linux 上部署 DeepSeek-r1:32/70b:解决下载中断问题

【linux】在 Linux 上部署 DeepSeek-r1:32/70b:解决下载中断问题 【承接商业广告,如需商业合作请+v17740568442】 文章目录 【linux】在 Linux 上部署 DeepSeek-r1:32/70b:解决下载中断问题问题描述:解决方法方法一:手动中断并重启下载方法二:使用 Bash 脚本自动化下载在…...

机器学习所需要的数学知识【01】

总览 导数 行列式 偏导数 概理论 凸优化-梯度下降 kkt条件...

4.【线性代数】——矩阵的LU分解

四 矩阵的LU分解 1. AB的逆矩阵2. 转置矩阵3. ALU3.1 2x2矩阵3.2 3x3矩阵3.3 nxn的矩阵分解的次数? 1. AB的逆矩阵 { ( A B ) ( B − 1 A − 1 ) I ( B − 1 A − 1 ) ( A B ) I ⇒ ( A B ) − 1 B − 1 A − 1 \begin{cases} (AB)(B^{-1}A^{-1}) I\\ (B^{-1}A^…...

【清晰教程】本地部署DeepSeek-r1模型

【清晰教程】通过Docker为本地DeepSeek-r1部署WebUI界面-CSDN博客 目录 Ollama 安装Ollama DeepSeek-r1模型 安装DeepSeek-r1模型 Ollama Ollama 是一个开源工具,专注于简化大型语言模型(LLMs)的本地部署和管理。它允许用户在本地计算机…...

Spring Cloud工程搭建

目录 工程搭建 搭建父子工程 创建父工程 Spring Cloud版本 创建子项目-订单服务 声明项⽬依赖 和 项⽬构建插件 创建子项目-商品服务 声明项⽬依赖 和 项⽬构建插件 工程搭建 因为拆分成了微服务,所以要拆分出多个项目,但是IDEA只能一个窗口有一…...

TDengine 快速体验(Docker 镜像方式)

简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

对WWDC 2025 Keynote 内容的预测

借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...

HTML前端开发:JavaScript 常用事件详解

作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践

作者:吴岐诗,杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言:融合数据湖与数仓的创新之路 在数字金融时代,数据已成为金融机构的核心竞争力。杭银消费金…...

Unity UGUI Button事件流程

场景结构 测试代码 public class TestBtn : MonoBehaviour {void Start(){var btn GetComponent<Button>();btn.onClick.AddListener(OnClick);}private void OnClick(){Debug.Log("666");}}当添加事件时 // 实例化一个ButtonClickedEvent的事件 [Formerl…...

根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的----NTFS源代码分析--重要

根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的 第一部分&#xff1a; 0: kd> g Breakpoint 9 hit Ntfs!ReadIndexBuffer: f7173886 55 push ebp 0: kd> kc # 00 Ntfs!ReadIndexBuffer 01 Ntfs!FindFirstIndexEntry 02 Ntfs!NtfsUpda…...

从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障

关键领域软件测试的"安全密码"&#xff1a;Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力&#xff0c;从金融交易到交通管控&#xff0c;这些关乎国计民生的关键领域…...

springboot 日志类切面,接口成功记录日志,失败不记录

springboot 日志类切面&#xff0c;接口成功记录日志&#xff0c;失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...

Python 高级应用10:在python 大型项目中 FastAPI 和 Django 的相互配合

无论是python&#xff0c;或者java 的大型项目中&#xff0c;都会涉及到 自身平台微服务之间的相互调用&#xff0c;以及和第三发平台的 接口对接&#xff0c;那在python 中是怎么实现的呢&#xff1f; 在 Python Web 开发中&#xff0c;FastAPI 和 Django 是两个重要但定位不…...

Java并发编程实战 Day 11:并发设计模式

【Java并发编程实战 Day 11】并发设计模式 开篇 这是"Java并发编程实战"系列的第11天&#xff0c;今天我们聚焦于并发设计模式。并发设计模式是解决多线程环境下常见问题的经典解决方案&#xff0c;它们不仅提供了优雅的设计思路&#xff0c;还能显著提升系统的性能…...