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();

外观模式的优缺点

优点

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

缺点

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

总结

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/70048.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【性能测试】如何理解“10个线程且10次循环“的请求和“100线程且1次循环“的请求

在性能测试中,我们常常会见到不同的并发配置:比如“10个线程且10次循环”与“100线程且1次循环”。乍一看,这两个设置的总请求数都是100次,但它们对系统的压力和测试场景却截然不同。了解其中的区别,能帮助你更精准地模…

Spring Boot 实战:轻松实现文件上传与下载功能

目录 一、引言 二、Spring Boot 文件上传基础 (一)依赖引入 (二)配置文件设置 (三)文件上传接口编写 (一)文件类型限制 (二)文件大小验证 &#xff0…

【Golang】GC探秘/写屏障是什么?

之前写了 一篇【Golang】内存管理 ,有了很多的阅读量,那么我就接着分享一下Golang的GC相关的学习。 由于Golang的GC机制一直在持续迭代,本文叙述的主要是Go1.9版本及以后的GC机制,该版本中Golang引入了 混合写屏障大幅度地优化了S…

DeepSeek教unity------MessagePack-03

数据契约兼容性 你可以使用 [DataContract] 注解代替 [MessagePackObject]。如果类型用 DataContract 进行注解,可以使用 [DataMember] 注解代替 [Key],并使用 [IgnoreDataMember] 代替 [IgnoreMember]。 然后,[DataMember(Order int)] 的…

【对比】Pandas 和 Polars 的区别

Pandas vs Polars 对比表 特性PandasPolars开发语言Python(Cython 实现核心部分)Rust(高性能系统编程语言)性能较慢,尤其在大数据集上(内存占用高,计算效率低)极快,利用…

百度千帆平台对接DeepSeek官方文档

目录 第一步:注册账号,开通千帆服务 第二步:创建应用,获取调用秘钥 第三步:调用模型,开启AI对话 方式一:通过API直接调用 方式二:使用SDK快速调用 方式三:在千帆大模…

49. c++计时器

为了测试某段特定代码的执行时间&#xff0c;体现代码的性能&#xff0c;可以使用计时器对代码段计时。下面使用std::chrono中的api编写简单案例&#xff1a; // // main.cpp // HelloWorld // // Created by on 2024/11/28. //#include <iostream> #include <vec…

Natural Language Processing NLP

NLP 清晰版本查看 Sentence segmentation (split)Tokenisation (split)Named entity recognition (combine) 概念主要內容典型方法Distributional Semantics&#xff08;分佈式語義&#xff09;&#xff08;分銷語義&#xff08;分佈式語義&#xff09;單詞的語義來自於它的…

Linux中线程创建,线程退出,线程接合

线程的简单了解 之前我们了解过 task_struct 是用于描述进程的核心数据结构。它包含了一个进程的所有重要信息&#xff0c;并且在进程的生命周期内保持更新。我们想要获取进程相关信息往往从这里得到。 在Linux中&#xff0c;线程的实现方式与进程类似&#xff0c;每个线程都…

HarmonyOS:使用List实现分组列表(包含粘性标题)

一、支持分组列表 在列表中支持数据的分组展示&#xff0c;可以使列表显示结构清晰&#xff0c;查找方便&#xff0c;从而提高使用效率。分组列表在实际应用中十分常见&#xff0c;如下图所示联系人列表。 联系人分组列表 在List组件中使用ListItemGroup对项目进行分组&#…

django上传文件

1、settings.py配置 # 静态文件配置 STATIC_URL /static/ STATICFILES_DIRS [BASE_DIR /static, ]上传文件 # 定义一个视图函数&#xff0c;该函数接收一个 request 参数 from django.shortcuts import render # 必备引入 import json from django.views.decorators.http i…

【前端知识】浏览器兼容方案polyfill

浏览器兼容方案polyfill 什么是 Polyfill&#xff1f;Polyfill 的作用Polyfill 的工作原理1. **特性检测**2. **加载 Polyfill**3. **模拟实现** Polyfill 的常见场景Polyfill 的使用方式Polyfill 的优缺点优点缺点 常见的 Polyfill 库总结 什么是 Polyfill&#xff1f; Polyf…

C#学习之DateTime 类

目录 一、DateTime 类的常用方法和属性的汇总表格 二、常用方法程序示例 1. 获取当前本地时间 2. 获取当前 UTC 时间 3. 格式化日期和时间 4. 获取特定部分的时间 5. 获取时间戳 6. 获取时区信息 三、总结 一、DateTime 类的常用方法和属性的汇总表格 在 C# 中&#x…

dedecms 开放重定向漏洞(附脚本)(CVE-2024-57241)

免责申明: 本文所描述的漏洞及其复现步骤仅供网络安全研究与教育目的使用。任何人不得将本文提供的信息用于非法目的或未经授权的系统测试。作者不对任何由于使用本文信息而导致的直接或间接损害承担责任。如涉及侵权,请及时与我们联系,我们将尽快处理并删除相关内容。 0x0…

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

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

AWS 前端自动化部署流程指南

本文详细介绍从前端代码开发到 AWS 自动化部署的完整流程。 一、流程概览 1.1 部署流程图 #mermaid-svg-nYg7k6L5IKVBjDtr {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-nYg7k6L5IKVBjDtr .error-icon{fill:#552…

Office word打开加载比较慢处理方法

1.添加safe参数 ,找到word启动项,右击word,选择属性 , 添加/safe , 应用并确定 2.取消加载项,点击文件,点击选项 ,点击加载项,点击转到,取消所有勾选,确定。

大数据SQL调优专题——Spark执行原理

引入 在深入MapReduce中有提到&#xff0c;MapReduce虽然通过“分而治之”的思想&#xff0c;解决了海量数据的计算处理问题&#xff0c;但性能还是不太理想&#xff0c;这体现在两个方面&#xff1a; 每个任务都有比较大的overhead&#xff0c;都需要预先把程序复制到各个 w…

MYSQL下载安装及使用

MYSQL官网下载地址&#xff1a;https://downloads.mysql.com/archives/community/ 也可以直接在服务器执行指令下载&#xff0c;但是下载速度比较慢。还是自己下载好拷贝过来比较快。 wget https://dev.mysql.com/get/Downloads/mysql-5.7.38-linux-glibc2.12-x86_64.tar.gz 1…

CentOS 7.8 安装MongoDB 7 副本集(Replica Set)

文章目录 1 环境假设步骤1&#xff1a;在两台服务器上安装MongoDB步骤2&#xff1a;配置副本集步骤3&#xff1a;初始化副本集步骤4&#xff1a;验证副本集配置步骤5&#xff1a;设置安全性&#xff08;可选&#xff09;扩展配置示例&#xff1a;最佳实践&#xff1a;仲裁节点步…