【设计模式】结构型

结构型设计模式

相关文章

设计模式 创建型


一、适配器模式

适配器模式(Adapter Pattern)是一种结构型设计模式,用于将一个类的接口转换成客户端所期望的另一个接口,以解决不兼容接口之间的问题。适配器模式允许不同接口的类协同工作,而无需修改其原始代码。

适配器模式通常涉及三个角色:目标接口(Target Interface)、适配器(Adapter)和被适配者(Adaptee)。

  • 目标接口(Target Interface) 定义了客户端期望的接口,适配器模式将被适配者的接口转换成目标接口。
  • 适配器(Adapter) 实现了目标接口,并且持有一个被适配者的实例,负责将目标接口的方法委派给被适配者。
  • 被适配者(Adaptee) 是需要被转换成目标接口的类,其接口与目标接口不兼容。

下面是适配器模式的一个示例,假设我们有一个旧系统中的类 LegacyPrinter,它有一个不兼容的 print 方法,而我们希望将其适配成一个符合新系统中 Printer 接口的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 被适配者:旧系统中的打印机类
class LegacyPrinter {
constructor() {
this.printMessage = function(message) {
console.log("Legacy Printer: " + message);
};
}
}

// 目标接口:新系统中的打印机接口
class Printer {
constructor() {
this.print = function(message) {};
}
}

// 适配器:将旧系统中的打印机适配成新系统中的打印机接口
class LegacyPrinterAdapter extends Printer {
constructor() {
super();
this.legacyPrinter = new LegacyPrinter();
this.print = function(message) {
this.legacyPrinter.printMessage(message);
};
}
}

// 使用适配器将旧系统中的打印机转换成新系统中的打印机接口
const printer = new LegacyPrinterAdapter();
printer.print("Hello, World!");

输出

1
Legacy Printer: Hello, World!

二、装饰器模式

装饰器模式(Decorator Pattern)是一种结构型设计模式,允许向对象动态地添加新功能,同时不改变其原始类的结构。该模式通过将对象封装在一个装饰器对象中,以及将装饰器对象链式地组合起来,实现功能的动态添加和组合。

装饰器模式通常涉及以下角色:

  • 组件(Component): 定义一个抽象接口,可以是一个抽象类或接口,也可以是一个具体类。
  • 具体组件(Concrete Component): 实现了组件接口的具体类,是被装饰的对象。
  • 装饰器(Decorator): 继承了组件接口,并持有一个对组件的引用,其本身也实现了组件接口。装饰器可以在调用被装饰对象的同时,增加新的功能。

下面是一个简单的装饰器模式的示例,假设我们有一个咖啡店,可以制作各种咖啡,我们想要为咖啡添加不同的调料,比如牛奶、糖浆或者摩卡:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// 装饰器基类
class CoffeeDecorator {
constructor(coffee) {
this.coffee = coffee;
this.descriptionValue = ""; // 初始化描述属性为空字符串
}

cost() {
return this.coffee.cost();
}

description() {
return this.descriptionValue; // 返回当前装饰器的描述
}
}

// 具体的装饰器类
class MilkDecorator extends CoffeeDecorator {
constructor(coffee) {
super(coffee);
this.costAddition = 0.5; // 添加牛奶的价格
this.descriptionValue = "Milk"; // 设置自己的描述
}

cost() {
return this.coffee.cost() + this.costAddition;
}
}

class SyrupDecorator extends CoffeeDecorator {
constructor(coffee) {
super(coffee);
this.costAddition = 0.3; // 添加糖浆的价格
this.descriptionValue = "Syrup"; // 设置自己的描述
}

cost() {
return this.coffee.cost() + this.costAddition;
}
}

class MochaDecorator extends CoffeeDecorator {
constructor(coffee) {
super(coffee);
this.costAddition = 0.7; // 添加摩卡的价格
this.descriptionValue = "Mocha"; // 设置自己的描述
}

cost() {
return this.coffee.cost() + this.costAddition;
}
}

// 使用示例
let coffee = new Coffee();
console.log(coffee.description() + ": $" + coffee.cost());

coffee = new MilkDecorator(coffee);
console.log(coffee.description() + ": $" + coffee.cost());

coffee = new SyrupDecorator(coffee);
console.log(coffee.description() + ": $" + coffee.cost());

coffee = new MochaDecorator(coffee);
console.log(coffee.description() + ": $" + coffee.cost());

输出

1
2
3
4
Coffee: $1
Milk: $1.5
Syrup: $1.8
Mocha: $2.5

三、代理模式

代理模式(Proxy Pattern)是一种结构型设计模式,允许在访问对象时引入一定程度的间接性,以控制对对象的访问。

代理模式通常涉及以下角色:

  • 主题(Subject): 定义了真实对象和代理对象的共同接口,客户端可以通过这个接口访问真实对象或代理对象。
  • 真实对象(Real Subject): 实现了主题接口,是被代理的对象,代理模式的最终目的是要访问和控制这个对象。
  • 代理对象(Proxy): 实现了主题接口

以下是一个简单的代理模式的示例,假设我们有一个图片接口 Image,包含加载图片和显示图片的方法,以及一个真实的图片类 RealImage 和一个代理类 ProxyImage:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 图片接口
class Image {
display() {}
}

// 真实图片类
class RealImage extends Image {
constructor(filename) {
super();
this.filename = filename;
this.loadFromDisk();
}

display() {
console.log("Displaying " + this.filename);
}

loadFromDisk() {
console.log("Loading " + this.filename + " from disk");
}
}

// 代理图片类
class ProxyImage extends Image {
constructor(filename) {
super();
this.filename = filename;
this.realImage = null;
}

display() {
if (!this.realImage) {
this.realImage = new RealImage(this.filename);
}
this.realImage.display();
}
}

// 使用示例
const image = new ProxyImage("image.jpg");

// 图片未被加载,只显示文件名
image.display();

输出

1
2
Loading image.jpg from disk
Displaying image.jpg

四、组合模式

组合模式(Composite Pattern)是一种结构型设计模式,它允许你将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得客户端可以统一地处理单个对象和组合对象,而不需要区分它们的类型。

在组合模式中,通常会有以下角色:

  • 组件(Component): 定义了统一的接口,可以是抽象类或接口,用于表示组合中的所有对象,包括叶子节点和容器节点。
  • 叶子节点(Leaf): 表示组合中的叶子对象,它没有子对象,实现了组件接口。
  • 容器节点(Composite): 表示组合中的容器对象,可以包含叶子节点和其他容器节点,实现了组件接口,并维护了一个子组件列表。

组合模式的核心思想是通过统一的接口来处理组合中的所有对象,这样客户端就可以像处理单个对象一样来处理整个树形结构。

让我们通过一个简单的文件系统的例子来说明组合模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 组件接口
class Component {
constructor(name) {
this.name = name;
}

display() {}
}

// 叶子节点
class File extends Component {
display() {
console.log("File: " + this.name);
}
}

// 容器节点
class Folder extends Component {
constructor(name) {
super(name);
this.children = [];
}

add(component) {
this.children.push(component);
}

remove(component) {
const index = this.children.indexOf(component);
if (index !== -1) {
this.children.splice(index, 1);
}
}

display() {
console.log("Folder: " + this.name);
this.children.forEach(child => child.display());
}
}

// 使用示例
const root = new Folder("Root");

const folder1 = new Folder("Folder 1");
folder1.add(new File("File 1-1"));
folder1.add(new File("File 1-2"));

const folder2 = new Folder("Folder 2");
folder2.add(new File("File 2-1"));
folder2.add(new File("File 2-2"));

root.add(folder1);
root.add(folder2);
root.add(new File("File 3"));

root.display();

输出

1
2
3
4
5
6
7
8
Folder: Root
Folder: Folder 1
File: File 1-1
File: File 1-2
Folder: Folder 2
File: File 2-1
File: File 2-2
File: File 3

五、桥接模式

桥接模式(Bridge Pattern)是一种结构型设计模式,它将抽象部分与实现部分分离,使它们可以独立地变化。桥接模式通过将抽象和实现分离,使得它们可以独立地进行变化和扩展,而不会相互影响。

在桥接模式中,通常会有两个维度的抽象:

  • 抽象部分(Abstraction): 它定义了抽象的接口和行为,通常会包含一个指向实现部分的引用,这样抽象部分就可以调用实现部分的具体功能。
  • 实现部分(Implementor): 它定义了实现的接口和行为,抽象部分的引用可以将抽象部分的请求委派给实现部分进行处理。

假设我们要设计一个跨平台的图形界面,支持不同的操作系统(例如 Windows 和 MacOS)和不同的图形控件(例如按钮、文本框等)。我们希望能够灵活地组合不同的操作系统和图形控件,而不需要修改已有的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 抽象部分
class UIControl {
constructor(platform) {
this.platform = platform;
}

draw() {
this.platform.drawUIControl();
}
}

// 实现部分 - Windows
class WindowsUIControl {
drawUIControl() {
console.log("Drawing Windows UI Control");
}
}

// 实现部分 - MacOS
class MacOSUIControl {
drawUIControl() {
console.log("Drawing MacOS UI Control");
}
}

// 使用示例
const windowsUIControl = new UIControl(new WindowsUIControl());
windowsUIControl.draw(); // 输出: Drawing Windows UI Control

const macOSUIControl = new UIControl(new MacOSUIControl());
macOSUIControl.draw(); // 输出: Drawing MacOS UI Control

输出

1
2
Drawing Windows UI Control
Drawing MacOS UI Control

六、外观模式

外观模式(Facade Pattern)是一种结构型设计模式,它提供了一个统一的接口,用来访问子系统中的一群接口。外观模式定义了一个高层接口,使得客户端可以简化与复杂系统的交互,而不需要了解系统内部的具体实现细节。

假设我们正在开发一个音响系统,它包含了多个子系统,如播放器、音量控制器、功放等。每个子系统都有自己的接口和实现,而客户端需要与这些子系统进行交互。在没有外观模式的情况下,客户端需要了解每个子系统的接口和调用方式,这样会增加客户端的复杂性。而通过外观模式,我们可以提供一个简单的接口给客户端使用,使得客户端可以轻松地与整个音响系统进行交互。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// 播放器子系统
class Player {
play() {
console.log("Playing...");
}

pause() {
console.log("Pausing...");
}

stop() {
console.log("Stopping...");
}
}

// 音量控制器子系统
class VolumeController {
mute() {
console.log("Muting...");
}

setVolume(volume) {
console.log("Setting volume to", volume);
}
}

// 功放子系统
class Amplifier {
powerOn() {
console.log("Powering on...");
}

powerOff() {
console.log("Powering off...");
}
}

// 音响外观类
class AudioFacade {
constructor() {
this.player = new Player();
this.volumeController = new VolumeController();
this.amplifier = new Amplifier();
}

playMusic() {
this.amplifier.powerOn();
this.player.play();
}

pauseMusic() {
this.player.pause();
}

stopMusic() {
this.player.stop();
this.amplifier.powerOff();
}

adjustVolume(volume) {
this.volumeController.setVolume(volume);
}

mute() {
this.volumeController.mute();
}
}

// 使用示例
const audioFacade = new AudioFacade();
audioFacade.playMusic(); // 输出: Powering on... \n Playing...
audioFacade.adjustVolume(50); // 输出: Setting volume to 50
audioFacade.mute(); // 输出: Muting...
audioFacade.pauseMusic(); // 输出: Pausing...
audioFacade.stopMusic(); // 输出: Stopping... \n Powering off...

输出

1
2
3
4
5
6
7
Powering on...
Playing...
Setting volume to 50
Muting...
Pausing...
Stopping...
Powering off...

七、亨元模式

享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享对象来最大程度地减少内存使用和提高性能。在享元模式中,对象被分为内部状态(Intrinsic State)和外部状态(Extrinsic State)两部分,其中内部状态可以被多个对象共享,而外部状态则必须在对象之间单独存储。

假设我们正在开发一个游戏,游戏中有许多树木对象,每个树木对象都有自己的位置和类型。在游戏中,有许多相同类型的树木对象,它们的外部状态(位置)不同,但内部状态(类型)是相同的。在没有使用享元模式的情况下,每个树木对象都需要单独存储其类型信息,这样会占用大量的内存。而通过享元模式,我们可以共享相同类型的树木对象,从而节省内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 树木对象
class Tree {
constructor(type) {
this.type = type;
}

render(x, y) {
console.log(`Rendering a ${this.type} tree at (${x}, ${y})`);
}
}

// 树木工厂
class TreeFactory {
constructor() {
this.treeTypes = {};
}

getTreeType(type) {
if (!this.treeTypes[type]) {
this.treeTypes[type] = new Tree(type);
}
return this.treeTypes[type];
}
}

// 游戏场景
class GameScene {
constructor() {
this.trees = [];
this.treeFactory = new TreeFactory();
}

addTree(type, x, y) {
const treeType = this.treeFactory.getTreeType(type);
this.trees.push({ type: treeType, x, y });
}

renderScene() {
this.trees.forEach(tree => {
tree.type.render(tree.x, tree.y);
});
}
}

// 使用示例
const gameScene = new GameScene();
gameScene.addTree("pine", 10, 20);
gameScene.addTree("oak", 30, 40);
gameScene.addTree("pine", 50, 60);

gameScene.renderScene();

输出

1
2
3
Rendering a pine tree at (10, 20)
Rendering a oak tree at (30, 40)
Rendering a pine tree at (50, 60)

喜欢这篇文章?打赏一下支持一下作者吧!
【设计模式】结构型
https://www.cccccl.com/20220807/设计模式/设计模式 结构型/
作者
Jeffrey
发布于
2022年8月7日
许可协议