【设计模式】02-理解常见设计模式-结构型模式

上一篇,我们介绍了设计模式-创建型模式的内容,并给出了相关代码示范。

这一篇我们接着介绍剩下的内容之一**"结构型模式"**


一、概述

结构型模式主要用于处理类或对象的组合,以获得新的功能或实现更灵活的结构

二、常见的结构型模式

1、适配器模式(Adapter Pattern)

将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

简单理解:适配器模式就像是一个转接头,当两个接口不兼容时,通过适配器将一个接口转换成另一个接口,使得原本不能一起工作的类可以协同工作。比如你有一个安卓线,但是只有苹果充电头,这时就需要一个适配器来完成适配。

代码示范

javascript 复制代码
// 旧接口类
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

代码分析:

javascript 复制代码
class OldInterface {
    specificRequest() {
        return 'Old interface request';
    }
}
  • 这是一个旧的接口类,里面有一个方法 specificRequest
  • 当调用 specificRequest 方法时,它会返回一个字符串 'Old interface request'。这个类就像是一个旧的工具,有它自己特定的功能。
javascript 复制代码
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)

动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。

解释 :装饰器模式就像给人穿衣服,你可以在不改变人的本质的情况下,通过添加不同的衣服(装饰器)来改变人的外观和功能。在编程中,就是在不改变原有对象结构的前提下,动态地给对象添加额外的职责。

代码示范以及解释

javascript 复制代码
// 基础对象类
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)

为其他对象提供一种代理以控制对这个对象的访问。

解释:代理模式就像是一个中介,当你需要访问某个对象时,不直接访问该对象,而是通过代理对象来访问。代理对象可以在访问对象前后添加一些额外的操作,比如权限验证、缓存等。就像你买房,不直接和卖家联系,而是通过房产中介来完成交易。

代码示范以及解释

javascript 复制代码
// 真实对象类
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)

为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

解释:外观模式就像是一个前台接待员,当你进入一个复杂的系统时,不需要了解系统内部的各个子系统的具体操作,只需要和前台接待员沟通,前台接待员会帮你处理所有的事情。在编程中,就是为子系统中的一组接口提供一个一致的界面,使得子系统更加容易使用。

代码示范以及解释

javascript 复制代码
// 子系统类 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

三、小结

设计模式在编程当中还是挺重要的,优点包括但不限于:代码复用性更高、可维护性更强、可扩展性更好。有时间可以花点时间学习/复习一下,相信对我们的编程技术和编程思维会有很多进步~

到此,我们已经完成【设计模式】专栏的前两篇了,还有最后一篇的内容"行为型模式",关注我,及时获取最新文章信息~

相关推荐
云徒川1 小时前
【设计模式】原型模式
java·设计模式·原型模式
huang_xiaoen8 小时前
java设计模式之桥接模式(重生之我在地府当孟婆)
设计模式·桥接模式
HappyGame028 小时前
设计模式-观察者模式
观察者模式·设计模式
渊渟岳9 小时前
掌握设计模式--解释器模式
设计模式
牵牛老人19 小时前
C++设计模式-责任链模式:从基本介绍,内部原理、应用场景、使用方法,常见问题和解决方案进行深度解析
c++·设计模式·责任链模式
肥仔哥哥193020 小时前
设计模式分类与定义(高软55)
设计模式·软考·高软·设计模式分类
云徒川1 天前
【设计模式】过滤器模式
windows·python·设计模式
找了一圈尾巴1 天前
设计模式(结构性)-代理模式
设计模式·代理模式
渊渟岳1 天前
掌握设计模式--模板方法模式
设计模式
程序员JerrySUN2 天前
设计模式 Day 2:工厂方法模式(Factory Method Pattern)详解
设计模式·工厂方法模式