JS设计模式之外观模式:简化复杂系统调用的利器

news2024/9/23 7:30:23

image.png

一、了解外观模式

概念

外观模式Facade Pattern)是一种结构型设计模式,它提供了一个简单的接口,隐藏了一个复杂系统的内部复杂性,使得客户端可以通过该接口与系统进行交互,而无需了解系统内部的具体实现细节。

外观模式可以将一个复杂系统分解成多个子系统或模块,然后使用一个外观类作为系统的统一接口,为客户端提供所需的功能。客户端只需要与外观类进行交互,而不需要直接与子系统进行交互。

作用

  1. 简化调用:将复杂的系统调用过程简化为几个简单的方法调用。通过封装系统的复杂性,可以更轻松地使用系统功能。

  2. 解耦合:减少客户端与子系统之间的耦合度。客户端只需与外观类进行交互,而不需要了解各个子系统之间的关系和具体实现细节。

  3. 提高灵活性:通过外观类,可以对复杂系统进行适当的封装和抽象,使得系统内部的变化对客户端的影响降低。当系统发生改变时,只需调整外观类即可,而不需要修改客户端代码。

  4. 提高可维护性:外观模式将系统的实现细节封装在一个外观类中,使得系统更易于维护和扩展。对于客户端来说,只需关注外观类的接口,而不需要关心系统的内部变化。

总之,外观模式可以帮助我们简化复杂系统的调用过程,提供一个统一的、简化的接口给客户端使用,减耦合度并提高代码的可维护性。

二、外观模式的核心思想

基本原理和关键要素

JavaScript 中,外观模式是一种结构型设计模式,它通过提供一个简单的接口来隐藏复杂的子系统或的复杂性。以下是外观模式的基本原理和关键要素:

  1. 基本原理

    • 外观类(Facade Class):外观类是外观模式的核心。它封装了子系统的复杂功能和接口,并提供了简洁、易于使用的接口给客户端。

    • 子系统类(Subsystems Class):子系统类包含了系统中的各个子系统或者模块,它们实现了具体的功能。外观类通过与子系统类交互来完成具体的功能。

  2. 关键要素

    • 外观类接口(Facade Interface):外观类提供了一组简单的接口方法,用于装和暴露子系统复杂功能。这些接口方法通常与具体的子系统类相关联。

    • 子系统类(Subsystems):子系统类实现了具体的功能,并包含一些具体的方法。外观类通过与子系统类进行交互来完成具体的功能。

    • 客户端(Client):客户端使用外观类提供的简化口来调用子系统的功能。客户端不需要了解和依赖子系统的具体实现,只需要通过外观类来访问系统的功能。

使用外观模式关键点在于外观类的设计。外观类需要将子系统的复杂性隐藏起来,提供一个简洁、易用的接口给客户端。在实现外观类时,需要考虑子系统的组合和调用顺序,以及如何将复杂的功能封装为单的接口方法。

  1. 简单示例

假如有多个子系统类 SubSystem1SubSystem2...,每个子系统都有负责的功能,我们如何能做到不了解子系统的功能而去使用它呢?

image.png

第一步:定义子系统类

// 子系统类
class SubSystem1 {
  operation1() {
    console.log("Subsystem1 operation1");
  }
}

class SubSystem2 {
  operation2() {
    console.log("Subsystem2 operation2");
  }
}

第二步:定义外观类

// 外观类
class Facade {
  constructor(subsystem1, subsystem2) {
    this.subsystem1 = subsystem1;
    this.subsystem = subsystem2;
  }

  //外观类接口方法
  run() {
    this.subsystem1.operation1();
    this.subsystem2.operation2();
  }
}

第三步:客户端使用外观类,客户端与子系统解耦合。

// 客户端代码调用
const subsystem1 = new SubSystem1();
const subsystem2 = new SubSystem2();
// 外观类
const facade = new Facade(subsystem1, subsystem2);
facade.run();

在上述示例中,外观类 Facade 封装了子系统类 Subsystem1Subsystem2 的复杂功能,并提供了简洁的接口方法 run客户端使用。客户端需要通过外观类调用 run 方法,而不需要了解和依赖子系统的具体实现。

外观模式通过封装和简化复杂的子系统功能,供了一个简洁、易用的接口给客户端使用。通过使用外观模式,可以隐藏复杂性、简化调用过程、解耦合系统各部分,并提高代码的可维护性和可读性。

理解外观模式中的外观类

JavaScript 中,简单的说,外观类是一种封装复杂子系统的设计模式,它提供了一个简单的接口给客户端使用,隐藏了子系统的复杂性。外观类的职责包括以下几个方面:

  1. 封装子系统接口:外观类封装了子系统的复杂接口和方法。它通过与子系统类进行交互,将多个子系统的功能整合在一个统一的接口中,以便客户端使用。

  2. 提供简化的接口方法:外观类提供了一组简化的接口方法给客户端使用。这些接口方法尽可能简洁易用,能够满足客户端的需求。通过这些接口方法,客户端可以不需要了解子系统的具体实现,直接调用外观类提供的方法来完成复杂的功能。

  3. 调用子系统方法:外观类在自身的方法中调用子系统的方法。它了解和管理子系统类之间的调用顺序和关系。可以根据具体的业务逻辑来组织子系统的调用,在不同的方法中调用不同的子系统方法,实现具体的功能。

  4. 隐藏子系统的复杂性:外观类将复杂的子系统功能封装在自身的简化接口中,客户端不需要了解和关心子系统的复杂细节。通过外观类,客户端可以将复杂的调用过程简化为一行或几行代码。

三、实现外观模式

API 封装管理

以最简单的 API 网络请求封装类为例,通过外观类封装了网络请求的具体实现,提供了更简洁的接口给客户端使用,使得发送网络请求更加简单和清晰。

// 子系统类,模拟网络请求
class Http {
  request(url, method, data) {
    console.log(`Sending ${method} request to ${url} with data:`, data);
    // 发送网络请求的具体逻辑
  }
}

// 外观类,封装网络请求方法
class API {
  constructor() {
    this.http = new Http(); // 创建子系统对象
  }

  // 封装 GET 请求
  get(url, data) {
    this.http.request(url, "GET", data);
  }

  // 封装 POST 请求
  post(url, data) {
    this.http.request(url, "POST", data);
  }
}

// 客户端代码
const api = new API();

// 发送 GET 请求
api.get("httpsapi.example.com/users", { id: 123 });

// 发送 POST 请求
api.post("https://api.example.com/users", { name: "John", age: 25 });

在上述示例中,Http 类表示一个简单的网络请求子系统类。API 类则是外观类,它封装了网络请求的具体实现,提供了更简洁的接口给客户端使用。

通过创建 API 类的实例 api,客户端可以调用 getpost 方法,而不需要关心具体的网络请求细节。API 类的方法内部会调用 Http 类的 request 方法,实现真正的网络请求操作。

这样,通过封装网络请求的细节,我们在客户端只需与 API 类交互,使得发送网络请求更加简单和清晰。

订餐系统套餐服务

假设我们有一个订购套餐服务的系统,包括餐厅、配送服务和支付服务。外观模式的主要思想是创建一个外观类将这些子系统封装起来,提供一个简化的接口方法供客户端调用,隐藏各个子系统内部的复杂操作。如下代码示例:

Snipaste_2023-09-18_15-58-46.png

Snipaste_2023-09-18_15-58-46.png

1. 各个子系统类 - 餐厅、配送服务、支付服务。

// 子系统类 - 餐厅
class Restaurant {
  getPackageInfo(packageId) {
    // 模拟根据套餐ID获取套餐信息的逻辑
    if (packageId === "A") {
      return { name: "Package A", price: 10 };
    } else if (packageId === "B") {
      return { name: "Package B", price: 15 };
    } else {
      return null;
    }
  }
}

// 子系统类 - 配送服务
class DeliveryService {
  getDeliveryTime(address) {
    // 模拟根据地址获取配送时间和费用的逻辑
    const fee = address.includes("Beijing") ? 3 : 5;
    return { time: "2 hours", fee };
  }

  deliverPackage(packageInfo, address) {
    // 模拟配送套餐的逻辑
    console.log(`Package ${packageInfo.name} delivered to ${address}`);
    // 返回配送状态
    return "delivered";
  }
}

// 子系统类 - 支付服务
class PaymentService {
  processPayment(amount) {
    // 模拟支付金额的逻辑
    const isSuccessful = Math.random() < 0.8;
    return isSuccessful ? "success" : "failure";
  }
}

2. 定义外观类,将这些子系统封装起来,提供一个简化的接口方法 orderPackage

// 外观类
class FoodDelivery {
  constructor() {
    this.restaurant = new Restaurant();
    this.deliveryService = new DeliveryService();
    this.paymentService = new PaymentService();
  }
  // 订单
  orderPackage(packageId, address) {
    const packageInfo = this.restaurant.getPackageInfo(packageId);
    if (!packageInfo) {
      return "Package not available.";
    }

    const deliveryTime = this.deliveryService.getDeliveryTime(address);
    const totalPrice = packageInfo.price + deliveryTime.fee;

    const paymentStatus = this.paymentService.processPayment(totalPrice);
    if (paymentStatus !== "success") {
      return "Payment failed. Please try again.";
    }

    const deliveryStatus = this.deliveryService.deliverPackage(
      packageInfo,
      address
    );
    return `Package delivered successfully to ${address}.`;
  }
}

3. 客户端使用外观类。

// 客户端代码
const foodDelivery = new FoodDelivery();
const packageId = "A";
const address = "123 Main St, Beijing";
const result = foodDelivery.orderPackage(packageId, address);
console.log(result);

image.png

在上述示例中,我们创建了一个套餐服务的子系统,包括餐厅、配送服务和支付服务。外观类 FoodDelivery 将这些子系统封装起来,提供一个简化的接口方法 orderPackage,最终客户端使用使用时不需要关注各个子系统的功能,只需要调用接口方法 orderPackage即可。

在外观类中,我们首先调用餐厅的 getPackageInfo 方法来获取指定套餐的信息。如果套餐不存在,我们返回相应的错误信息。

接下来,我们利用配送服务的 getDeliveryTime 方法获取配送时间和费用。然后,根据套餐价格和配送费用,计算出总价。

接着,我们调用支付服务的 processPayment 方法来处理支付。如果支付失败,我们返回相应的错误信息。

最后,我们调用配送服务的 deliverPackage 方法来配送套餐,并返回成功配送的信息给客户端。

在客户端代码中,我们实例化了外观类 FoodDelivery,然后调用了 orderPackage 方法来订购套餐。客户端只需要与外观类交互,并不需要了解和依赖子系统的具体实现细节。

通过外观模式,我们将复杂的套餐服务操作封装在外观类中,并提供了一个简单的接口给客户端使用。客户端只需要通过外观类来订购套餐,不需要与子系统直接交互,从而简化了客户端的代码和逻辑。

四、优点与应用场景

外观模式的优点

  1. 简化客户端代码:外观模式提供了一个统一的接口,隐藏了子系统的复杂性,使客户端代码更加简洁和易于维护。

  2. 解耦客户端和子系统:外观模式将客户端与子系统解耦,客户端只需要与外观类进行交互,而不需要了解和依赖子系统的具体实现细节。这使得子系统可以独立变化,而不影响客户端。

  3. 提高安全性:通过外观模式,限制客户端直接访问子系统的某些功能,增加了系统的安全性。

外观模式的应用

在实际开发中,JavaScript 外观模式有很多应用场景,尤其在以下的场景下应用广泛:

  1. API 封装:在前端开发中,通过外观模式可以将对后端 API 的调用封装在一个统一的接口中。这样客户端只需要与外观类进行交互,而不需要关心具体的网络请求细节,如请求地址、数据处理等。这种方式可以使前端开发更加简洁和高效。

  2. UI 库封装:当使用复杂的 UI 库时,可以使用外观模式将 UI 组件的初始化、渲染、事件处理等功能封装起来。这样客户端只需要与外观类进行交互,简化了客户端代码并提高了代码的可维护性。

  3. 浏览器兼容性处理:在处理浏览器兼容性时,可以使用外观模式将不同浏览器下的兼容性处理封装起来。客户端只需要与外观类进行交互,而不需要关心具体的浏览器兼容性细节,简化了客户端代码,也方便在未来进行兼容性的调整。

  4. 第三方库的封装:当使用第三方库时,可以使用外观模式将其封装起来,提供更简洁的接口给客户端使用。这样可以隐藏底层库的复杂性,减少了客户端与库之间的直接依赖。同时,如果将来需要替换底层库,只需修改外观类的实现即可,而不影响客户端。

总结

在使用 JavaScript 外观模式时,有以下几个准则,注意这几个准则的正确运用将是我们实现外观模式简单易用的前提保障:

  1. 角色分配:清楚划分好外观类和子系统类的责任,确保每个类的职责明确且单一。

  2. 不破坏封装性:外观模式的目的是将子系统进行封装,向客户端提供一个简化的接口。

  3. 可扩展性考虑:在设计外观模式时,要考虑到子系统的可扩展性。即使子系统发生变化,外观类也无需修改,维护起来更加灵活。

  4. 不滥用外观模式:外观模式的目的是简化客户端的操作,但并不意味着在所有情况下都需要使用外观模式,避免滥用造成过度封装和增加不必要的复杂性。

  5. 兼容性:在使用外观模式时要考虑兼容性问题,尤其是涉及不同浏览器或不同版本的兼容性。确保外观类能够提供一致的接口,适配不同的环境,保证代码的可移植性和可靠性。

注意以上事项可以帮助更好地使用和设计 JavaScript 外观模式,提高代码的可维护性、易读性和扩展性。

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

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

相关文章

vue3 一次二次封装element-plus组件引发的思考

前言 在开发 Vue 项目中我们一般使用第三方 UI 组件库进行开发&#xff0c;如 Element-Plus、Element-ui、Ant-design等, 但是这些组件库提供的组件并不一定都能满足我们的日常开发需求&#xff0c;有时候我们需要实现的效果是直接使用组件库无法实现的&#xff0c;那么这时我…

TMS320F28335的基本电路设计

1.电源电路 2.时钟电路 3.复位电路 4.JTAG电路 5.外扩RAM电路 6.外扩Flash电路 7.GPIO电平转换电路 8.ADC调理电路 9.串口通信电路 10.CAN电路 11.I2C电路 12.BOOT启动电路 12.调试注意事项

如何判断一个系统的大小端的存储模式

1、什么是大小端 大小端&#xff1a;是指在计算机系统中&#xff0c;多字节数据&#xff08;如整数、浮点数等&#xff09;存储顺序的不同而不同的称呼。 在计算机内存中&#xff0c;数据是以字节为单位存储的。对于多字节数据&#xff0c;如4字节的整数&#xff0c;存储的时…

基于JAVA+SpringBoot+Vue的前后端分离企业oa管理系统

基于JAVASpringBootVue的前后端分离企业oa管理系统 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末附源码下载链接&#x1…

化工原料环保能源网站模板整站打包下载

图片在最下面 响应式新能源化工研究院网站模板.zip 营销型硅胶制品原料网站模板.zip 环境水务治理网站模板.zip 响应式新能源清洁能源公司网站模板.zip 环保废气废水处理工程类网站模板.zip 活性炭净化炭企业网站模板.zip 响应式新能源开发企业网站模板.zip 营销型塑料…

leaflet【十】实时增加轨迹点轨迹回放效果实现

实时轨迹回放 在前面有用leaflet-trackplayer实现了一个轨迹回放的效果&#xff0c;单击前往&#xff1a;轨迹回放效果&控制台控制轨迹运动效果 这篇文章主要是实现一下实时增加轨迹点&#xff0c;不改变原来运行轨迹和速度。这里是简易做了一个demo效果&#xff0c;大概…

django学习入门系列之第十点《A 案例: 员工管理系统5》

文章目录 7 模板的继承7.1 继承指令-占位符7.2 继承指令 - 继承符7.3 **想用继承的html的文件**7.4 简单模板的例子 8 用户管理8.1 展示日期数据8.2 转义文字&#xff08;应该会常用&#xff09;8.3 搜索并跨表8.4 模板语法 往期回顾 7 模板的继承 部门列表添加部门编辑部门 …

如何使用ChatGPT,完成学术论文文献综述的编写?

学境思源&#xff0c;一键生成论文初稿&#xff1a; AcademicIdeas - 学境思源AI论文写作 在学术研究中&#xff0c;文献综述是了解研究现状、辨识研究空白并为自己的研究奠定理论基础的关键环节。ChatGPT 可以在文献综述的编写过程中提供有效的支持&#xff0c;从文献搜集、批…

51单片机快速入门之延时代码 STC 51单片机

51单片机快速入门之延时代码 时序周期:简单的举例(早 中 晚) 时间基准:一个机器周期有 12个 振荡周期(晶振) 假设我们现在手上为12MHz晶振 单个振荡周期计算公式:T(时间周期)1/f(晶振的频率) 在处理频率相关的计算时&#xff0c;通常会使用赫兹&#xff08;Hz&#xff09;…

【Linux】【Vim】Vim 基础

Vim/Gvim 基础 文本编辑基础编辑操作符命令和位移改变文本重复改动Visual 模式移动文本(复制、粘贴)文本对象替换模式 光标移动以 word 为单位移动行首和行尾行内指定单字符移动到匹配的括号光标移动到指定行滚屏简单查找 /string标记 分屏vimdiff 文本编辑 基础编辑 Normal 模…

Unity让摄像机跟随物体的方法(不借助父子关系)

在Unity中&#xff0c;不使用子对象的方式让相机跟随物体移动&#xff0c;我们通过编写脚本来实现。下面放一个从工程中摘出来的的C#脚本示例&#xff0c;用于将相机绑定到一个Target对象上并跟随其移动&#xff1a; using UnityEngine; public class FollowCamera : MonoBeh…

Mac 上终端使用 MySql 记录

文章目录 下载安装终端进入 MySql常用操作查看数据库选择一个数据库查看当前选择的数据库Navcat 打开提示报错参考文章 下载安装 先下载社区版的 MySql 安装的过程需要设置 root 的密码&#xff0c;这个是要进入数据库所设定的&#xff0c;所以要记住 终端进入 MySql 首先输…

docker+docker-compose+gitlab

Docker安装 下载 下载docker二级制文件docker-20.10.9.tgz https://download.docker.com/linux/static/stable/x86_64/docker-20.10.9.tgz 上传到/data目录 解压文件 # cd /data # tar -zxf docker-20.10.9.tgz 移动解压出来的文件到/usr/bin目录 # mv docker/* /usr/b…

RAPIDS AI 加速制造业预测性维护效率

根据国际自动化协会&#xff08;ISA&#xff09;报告&#xff0c;每年有5%的工厂生产因机时间而受到损失。在另一种情况下&#xff0c;各行各业的制造商在全球范围内放弃了大约647亿美元&#xff0c;而相应的部分在生产中则接近13万亿美元。当前的挑战是预测这些机器的维护需求…

fastjson漏洞--以运维角度进行修复

文章目录 前言一、漏洞详情二、修复过程1.通过脚本方式修复1.1.脚本修复原理1.2.脚本演示1.3.执行脚本 2. 手动升级包2.1.修复步骤2.2.遇到的问题 前言 该漏洞是三个月前由安全团队扫描出来的&#xff0c;主要影响是: FastJSON是阿里巴巴的开源JSON解析库&#xff0c;它可以解…

机器人--手眼标定算法

教程 知乎1 CSDN博主 什么是手眼标定 eye_to_hand 相机不在机器人上-----相机坐标系相对于机器人基坐标系的转换矩阵不变&#xff1b; eye_in_hand 相机在机器人上-----相机坐标系相对于机器人工具坐标系的转换矩阵不变&#xff1b; 手眼标定的目的 目的&#xff1a;求解出…

模板语法

模板语法 {{.}} 模板语法都包含在 {{ 和 }} 中间&#xff0c;其中{{ . }}中的点表示当前对象。 当传入一个结构体对象时&#xff0c;可以根据 . 来访问结构体的对应字段。 当传入的变量是map时&#xff0c;也可以在模板文件中通过 . 根据key来取值。 main.go package maini…

S3C2440 ARM设备驱动(boot loader,kernel,rootfs)

一、开发板Linux启动需求 1、bootloader 为内核启动准备环境&#xff0c;并引导内核启动 2、kernel&#xff08;linux内核&#xff09; 操作系统的核心&#xff0c;&#xff08;狭义上的操作系统&#xff09; 3、rootfs 一堆有组织的文件 1. bootloader(一个裸机程序) 初始化C…

【MM24】【水下目标分割】Dual_SAM

论文&#xff1a;https://arxiv.org/abs/2404.04996 代码&#xff1a;https://github.com/Drchip61/Dual_SAM 点评 这篇文章介绍的改进SAM,面向海洋生物的分割。但是海洋图像易受噪声影响&#xff0c;论文中仅是通过一个gamma变换减弱了这种影响。双主干网络的参数量并没有提及…

2.队列和队列集

队列的本质就是环形buff,加了互斥操作,加了阻塞-唤醒. 1.介绍一下环形BUFF 2.阻塞和唤醒 在我们队列中是有一个读list 链表 和 一个写list链表 他们就是用来保存 读 或者 写阻塞 的任务 假设我们 就绪链表中有A 和 B两个任务 他们正常情况下都是轮流运行, 这个时候 A 想读队…