上一篇,我们介绍了设计模式-创建型模式的内容,并给出了相关代码示范。
这一篇我们接着介绍剩下的内容之一“结构型模式”
一、概述
结构型模式主要用于处理类或对象的组合,以获得新的功能或实现更灵活的结构。
二、常见的结构型模式
1、适配器模式(Adapter Pattern)
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
简单理解:适配器模式就像是一个转接头,当两个接口不兼容时,通过适配器将一个接口转换成另一个接口,使得原本不能一起工作的类可以协同工作。比如你有一个安卓线,但是只有苹果充电头,这时就需要一个适配器来完成适配。
代码示范
// 旧接口类
class OldInterface {
specificRequest() {
return 'Old interface request';
}
}
// 新接口
class NewInterface {
constructor() {
this.oldInterface = new OldInterface();
}
request() {
return this.oldInterface.specificRequest().replace('Old', 'New');
}
}
// 使用示例
const newObj = new NewInterface();
console.log(newObj.request()); // 输出: New interface request
代码分析:
class OldInterface {
specificRequest() {
return 'Old interface request';
}
}
- 这是一个旧的接口类,里面有一个方法
specificRequest
。- 当调用
specificRequest
方法时,它会返回一个字符串'Old interface request'
。这个类就像是一个旧的工具,有它自己特定的功能。
class NewInterface {
constructor() {
this.oldInterface = new OldInterface();
}
request() {
return this.oldInterface.specificRequest().replace('Old', 'New');
}
}
构造函数 constructor:
- 在创建
NewInterface
类的实例时,构造函数会被自动调用。- 它创建了一个
OldInterface
类的实例,并把这个实例赋值给this.oldInterface
。也就是说,新接口类持有了旧接口类的一个对象,方便后续使用旧接口的功能。request 方法:
- 这个方法是新接口提供的功能。
- 它首先调用
this.oldInterface.specificRequest()
,也就是调用旧接口的specificRequest
方法,得到字符串'Old interface request'
。- 然后使用
replace
方法把字符串中的'Old'
替换成'New'
,最终返回'New interface request'
。这个过程就像是把旧工具的输出做了一些修改,让它符合新的要求。
2、装饰器模式(Decorator Pattern)
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
解释:装饰器模式就像给人穿衣服,你可以在不改变人的本质的情况下,通过添加不同的衣服(装饰器)来改变人的外观和功能。在编程中,就是在不改变原有对象结构的前提下,动态地给对象添加额外的职责。
代码示范以及解释
// 基础对象类
class Coffee {
cost() {
return 5;
}
}
//这是一个基础的咖啡类,其中有一个 cost 方法。
//当调用 cost 方法时,它会返回基础咖啡的价格,这里设定为 5 元。可以把它想象成一杯没有任何配料的纯咖啡。
// 装饰器抽象类
class CoffeeDecorator {
constructor(coffee) {
this.coffee = coffee;
}
cost() {
return this.coffee.cost();
}
}
//构造函数 constructor:
//这个构造函数接收一个 coffee 对象作为参数,并将其赋值给 this.coffee。也就是说,装饰器类会持有一个要装饰的咖啡对象。
//cost 方法:
//该方法直接调用被装饰咖啡对象的 cost 方法并返回结果。这个类是所有具体装饰器的基类,它定义了装饰器的基本结构,但本身并没有添加额外的功能。
// 具体装饰器:牛奶
class MilkDecorator extends CoffeeDecorator {
cost() {
return this.coffee.cost() + 2;
}
}
//这个类继承自 CoffeeDecorator 类,它代表给咖啡添加牛奶这个配料。
//cost 方法先调用被装饰咖啡对象的 cost 方法得到原咖啡的价格,然后在此基础上加上 2 元(牛奶的价格),最终返回添加牛奶后咖啡的总成本。
// 具体装饰器:糖
class SugarDecorator extends CoffeeDecorator {
cost() {
return this.coffee.cost() + 1;
}
}
//同样继承自 CoffeeDecorator 类,代表给咖啡添加糖这个配料。
//cost 方法也是先获取原咖啡的价格,再加上 1 元(糖的价格),返回添加糖后咖啡的总成本。
// 使用示例
let coffee = new Coffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
console.log(coffee.cost()); // 输出: 8
整体运行解释
首先创建了一个 Coffee 类的实例 coffee,这是一杯基础的纯咖啡,价格为 5 元。
然后使用 MilkDecorator 对 coffee 进行装饰,此时 coffee 变成了添加了牛奶的咖啡,价格变为 5 + 2 = 7 元。
接着再用 SugarDecorator 对 coffee 进行装饰,此时 coffee 变成了既添加了牛奶又添加了糖的咖啡,价格变为 7 + 1 = 8 元。
最后调用 coffee.cost() 方法并将结果打印到控制台,输出为 8 元。
3、代理模式(Proxy Pattern)
为其他对象提供一种代理以控制对这个对象的访问。
解释:代理模式就像是一个中介,当你需要访问某个对象时,不直接访问该对象,而是通过代理对象来访问。代理对象可以在访问对象前后添加一些额外的操作,比如权限验证、缓存等。就像你买房,不直接和卖家联系,而是通过房产中介来完成交易。
代码示范以及解释
// 真实对象类
class RealImage {
constructor(filename) {
this.filename = filename;
this.loadFromDisk();
}
display() {
console.log(`Displaying ${this.filename}`);
}
loadFromDisk() {
console.log(`Loading ${this.filename}`);
}
}
//构造函数 constructor:
//接收一个 filename 参数,用于指定图片的文件名,并将其存储在 this.filename 中。
//调用 loadFromDisk 方法,模拟从磁盘加载图片的操作。这意味着只要创建 RealImage 对象,就会立即加载图片。
//display 方法:
//打印出正在显示指定文件名图片的信息,模拟图片显示的操作。
//loadFromDisk 方法:
//打印出正在加载指定文件名图片的信息,模拟从磁盘读取图片的过程。
// 代理对象类
class ProxyImage {
constructor(filename) {
this.filename = filename;
this.realImage = null;
}
display() {
if (!this.realImage) {
this.realImage = new RealImage(this.filename);
console.log("不存在,创建");
}
this.realImage.display();
}
}
//构造函数 constructor:
//接收一个 filename 参数,存储图片的文件名。
//初始化 this.realImage 为 null,表示还没有创建真实的图片对象。
//display 方法:
//首先检查 this.realImage 是否为 null。如果为 null,说明还没有创建真实的图片对象,此时调用 new //RealImage(this.filename) 创建真实的图片对象,并将其赋值给 this.realImage,这一步会触发图片的加载操作。
//然后调用 this.realImage.display() 方法来显示图片。由于已经判断过是否需要加载图片,所以后续再次调用 display 方法时,不会重复加载图片。
// 使用示例
const proxyImage = new ProxyImage('test.jpg');
// 第一次调用,会加载图片
proxyImage.display();
// 第二次调用,不会再次加载图片
proxyImage.display();
//创建一个 ProxyImage 对象 proxyImage,传入图片文件名 'test.jpg'。此时并没有加载图片,只是创建了代理对象。
//第一次调用 proxyImage.display() 时,因为 this.realImage 为 null,所以会创建 RealImage 对象,触发图片加载操作,然后显示图片。
//第二次调用 proxyImage.display() 时,由于 this.realImage 已经不为 null,不会再次创建 RealImage 对象,也就不会再次加载图片,直接显示图片。
4、外观模式(Facade Pattern)
为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
解释:外观模式就像是一个前台接待员,当你进入一个复杂的系统时,不需要了解系统内部的各个子系统的具体操作,只需要和前台接待员沟通,前台接待员会帮你处理所有的事情。在编程中,就是为子系统中的一组接口提供一个一致的界面,使得子系统更加容易使用。
代码示范以及解释
// 子系统类 1
class CPU {
startup() {
console.log('CPU startup');
}
shutdown() {
console.log('CPU shutdown');
}
}
//startup 方法:打印出 Memory startup,模拟内存启动的操作。
//shutdown 方法:打印出 Memory shutdown,模拟内存关闭的操作。
// 子系统类 2
class Memory {
startup() {
console.log('Memory startup');
}
shutdown() {
console.log('Memory shutdown');
}
}
// 方法同上,打印的语句不同而已
// 子系统类 3
class Disk {
startup() {
console.log('Disk startup');
}
shutdown() {
console.log('Disk shutdown');
}
}
// 方法同上,打印的语句不同而已
// 外观类
class ComputerFacade {
constructor() {
this.cpu = new CPU();
this.memory = new Memory();
this.disk = new Disk();
}
startup() {
this.cpu.startup();
this.memory.startup();
this.disk.startup();
}
shutdown() {
this.cpu.shutdown();
this.memory.shutdown();
this.disk.shutdown();
}
}
//构造函数 constructor:
//创建 CPU、Memory 和 Disk 类的实例,并分别存储在 this.cpu、this.memory 和 this.disk 中。
//startup 方法:
//依次调用 CPU、Memory 和 Disk 实例的 startup 方法,实现计算机启动时各个组件的启动操作。
//shutdown 方法:
//依次调用 CPU、Memory 和 Disk 实例的 shutdown 方法,实现计算机关闭时各个组件的关闭操作。
// 使用示例
const computer = new ComputerFacade();
computer.startup();
// 输出:
// CPU startup
// Memory startup
// Disk startup
computer.shutdown();
// 输出:
// CPU shutdown
// Memory shutdown
// Disk shutdown
三、小结
设计模式在编程当中还是挺重要的,优点包括但不限于:代码复用性更高、可维护性更强、可扩展性更好。有时间可以花点时间学习/复习一下,相信对我们的编程技术和编程思维会有很多进步~
到此,我们已经完成【设计模式】专栏的前两篇了,还有最后一篇的内容“行为型模式”,关注我,及时获取最新文章信息~