外观设计模式(Facade Design Pattern)
定义
一种结构性设计模式,旨在为复杂系统提供简单和统一的接口。
核心思想
外观设计模式通过创建一个高级别的接口或类,包装了一个或多个底层组件或子系统的复杂操作。 这个高级别接口隐藏了底层组件之间的复杂性,并提供了一个简化、统一的界面(接口),使得客户端可以更方便地使用系统。
作用
- 外观设计模式的主要目标是简化客户端与底层系统之间的交互,降低了客户端的复杂度并提高了其可用性。
- 同时,它也提供了一种隔离底层系统变化对客户端的影响,从而增加了系统的灵活性和可维护性。
参与者
外观(Facade):外观是一个高级别的接口或类,它封装了底层组件或子系统的复杂操作。外观提供了一个简单、统一的接口给客户端,隐藏了底层的复杂性。
子系统(Subsystem):子系统是一组相关的类或底层组件,负责完成特定的任务。这些子系统可能是独立的,但它们被外观所封装起来,客户端只需要与外观进行交互,而不需要直接与子系统交互。
客户端(Client):客户端是使用外观模式的代码。它通过与外观交互来完成特定的任务,而不需要了解底层子系统的复杂性。
应用场景: 外观设计模式在实际开发中有广泛应用
- 简化复杂的 API、框架或类库的使用
- 提供更友好的接口给开发者
- 将底层的复杂逻辑封装起来,使得系统的各个部分更加独立和可维护
具体来说
jQuery:jQuery 是一个流行的 JavaScript 库,它使用了外观设计模式。通过 jQuery,开发者可以使用简单的 API 来处理复杂的 DOM 操作和事件处理。
XMLHttpRequest:在原生 JavaScript 中进行 AJAX 请求时,常常使用 XMLHttpRequest 对象。该对象提供了一个简化的接口,隐藏了底层的复杂性,使得开发者能够更方便地发送和处理网络请求。
Bootstrap:Bootstrap 是一个广泛使用的前端框架,它提供了大量的 CSS 样式和 JavaScript 插件。这些插件封装了复杂的交互逻辑,让开发者能够通过简单的 API 来实现常见的 UI 功能,如模态框、轮播等。
Video.js:Video.js 是一个流行的 HTML5 视频播放器库,它提供了一个简单的 API 来管理视频播放和控制。通过 Video.js,开发者可以使用统一的接口来处理不同浏览器的兼容性问题,以及自定义视频播放器的样式和功能。
Google Maps API:Google Maps API 提供了一组简化的接口,使开发者能够在网站中集成地图和地理位置相关的功能。通过使用这些接口,开发者可以快速地添加地图、标记、路线规划等功能,而不需要处理复杂的地图数据和底层交互。
简单来说:封装 + 降低接口复杂度 = 外观设计模式
示例
// 子系统Product,Product是子系统或者底层组件,方法getName, getPrice用来完成特定的任务
class Product {
constructor(private name: string, private price: number) {}
public getName(): string {
return this.name;
}
public getPrice(): number {
return this.price;
}
}
// 子系统Inventory,Inventory是子系统或者底层组件,方法addProduct,getProductByName用来完成特定的任务
class Inventory {
private products: Product[] = [];
public addProduct(product: Product): void {
this.products.push(product);
}
public getProductByName(name: string): Product | undefined {
return this.products.find((p) => p.getName() === name);
}
}
// 子系统PaymentGateway,PaymentGateway是子系统或者底层组件,方法processPayment用来完成特定的任务
class PaymentGateway {
public processPayment(amount: number): void {
// 处理支付逻辑
console.log(`支付 ${amount} 元`);
}
}
// 外观类:提供封装了子系统方法的接口buyProduct
class Shop {
constructor(
private inventory: Inventory,
private paymentGateway: PaymentGateway
) {}
public buyProduct(productName: string): void {
const product = this.inventory.getProductByName(productName);
if (product) {
this.paymentGateway.processPayment(product.getPrice());
console.log(`购买了产品:${product.getName()}`);
} else {
console.log("无法找到指定的产品");
}
}
}
// 构造子系统实例product, inventory和paymentGateway
const product = new Product("手机", 1000);
const inventory = new Inventory();
inventory.addProduct(product);
const paymentGateway = new PaymentGateway();
// 构造外观类实例
const shop = new Shop(inventory, paymentGateway);
// 客户端只需要使用外观类实例上的方法即可,无需了解此方法的具体过程
shop.buyProduct("手机");
为什么说上面的代码符合外观模式呢?
- 子系统实例 product, inventory 和 paymentGateway 的构造过程可能是在别的地方完成的;
- 客户端只要能够拿到 nventory, paymentGateway 实例就可以通过 Shop 的实例上的 buyProduct 接口完成任务;
- 无需关心子系统之间复杂的关系和它们之间相互调用的逻辑。
外观设计模式和模板设计模式
- 在实现方式上,外观设计模式和模板设计模式的基本流程相同,但是侧重点不同;外观设计模式是一种结构型设计模式,强调的是信息传递使用的接口,而模板设计模式是一种行为型设计模式,强调的是信息的流动性;
- 其次,在实现上也有细微的差别,模板设计模式强调扩展类、集成、抽象接口这些概念,如果使用ts实现的话,最直观的特征就是有一个抽象类,实现了一个具体方法,这个方法调用了其他未实现的抽象方法,而这些抽象方法的实现要延迟到子类中去;而外观设计模式强调的是子系统,以及使子系统的复杂性不可见这个效果。
外观设计模式与 Typescript
使用 ts 的修饰器 private 可以方便的应用此设计模式
class Facade {
private doTaskA() {
console.log("task A completed!");
}
private doTaskB() {
console.log("task B completed!");
}
private doTaskC() {
console.log("task C completed!");
}
public doTask(task: string) {
const taskList = task.split("->");
if (!taskList?.length) return;
while (taskList.length) {
const localTask = taskList.pop();
this[`doTask${localTask}`]();
}
}
}
const f1 = new Facade();
f1.doTask("A->B->A->C->B");
f1.doTaskA();
- 上面的例子中,doTask 封装了 doTaskA,doTaskB,doTaskC; 向外提供了一个简单的接口 doTask;
- 可以看出来,在 ts 中实现外观设计模式是十分便利的!
经典外观例子
外观设计模式在jQuery中使用广泛,用来封装通用方法,提供在各个浏览器平台上都能使用的统一接口,如下所示:
function addEvent (dom, type, fn) {
if(dom.addEventListener){
dom.addEventListener(type, fn, false);
} else if (dom.attachEvent) {
dom.attachEvent('on' + type, fn);
} else {
dom['on' + type] = fn;
}
}
在上述代码中就封装了一个用来在不同浏览器中统一绑定事件的接口。