文章目录
- 结构型模式
- 简介
- 适配器模式
- 装饰器模式
- 代理模式
- 外观模式
- 桥接模式
- 组合模式
- 享元模式
结构型模式
简介
在设计模式中,结构型模式用于描述如何将对象和类组装成较大的结构,并灵活地处理对象之间的关系。
结构型模式包括以下几种:
- 适配器模式:用于连接不兼容的接口,让它们能够一起工作。
- 装饰器模式:动态地给对象添加额外的职责。
- 代理模式:为其他对象提供一种代理以控制对这个对象的访问。
- 外观模式:为复杂系统提供简化的接口,让客户端能够更容易地使用系统。
- 桥接模式:将抽象和实现分离开来,使得它们可以独立地变化。
- 组合模式:将对象组合成树形结构以表示“整体-部分”关系,客户端可以像操作单个对象一样来操作组合对象。
- 享元模式:共享对象,以支持大量的细粒度对象的复用。
这些结构型模式都有助于提高系统的灵活性、可扩展性和可维护性。例如,适配器模式可以将不兼容的接口转换为兼容的接口,解决实际开发中对象之间接口不兼容的问题;装饰器模式可以动态地添加更多职责,而无需改变对象本身的代码;代理模式可以控制对对象的访问,从而实现访问权限控制等功能。
尽管结构型模式中的每个模式都是独立的,但它们也常常被组合使用。例如,装饰器模式的实现中常常会用到适配器模式,而桥接模式的实现也常常会包含组合模式的思想。熟悉结构型模式可以帮助开发者更好地理解一些经典的开源框架,并且在实际项目中设计和实现更好的软件架构。
适配器模式
适配器模式(Adapter Pattern)是一种结构型设计模式,它允许不兼容的对象之间进行合作。适配器作为两个不兼容的对象之间的转换器,将一个对象的接口,转换成另一个对象需要的接口。通过适配器模式,我们可以将客户端代码与不同的对象解耦,从而提高了代码的复用性和灵活性。
一个简单的适配器模式的例子是:我们有一个圆孔和一个圆钉,但是我们需要将这个圆钉放进一个方孔里。我们可以使用一个方形适配器,将圆钉适配成方钉,使得它能够顺利地放进方孔中。
下面是使用 JavaScript 实现的简单的适配器模式的示例:
// 需要适配的类
class RoundPeg {
constructor(radius) {
this.radius = radius;
}
getRadius() {
return this.radius;
}
}
// 目标类
class SquareHole {
constructor(width) {
this.width = width;
}
getWidth() {
return this.width;
}
fits(peg) {
return this.getWidth() >= peg.getRadius() * Math.sqrt(2);
}
}
// 适配器类
class SquarePegAdapter {
constructor(peg) {
this.peg = peg;
}
getWidth() {
return this.peg.getRadius() * Math.sqrt(2);
}
}
// 客户端代码
const roundPeg = new RoundPeg(5);
const squareHole = new SquareHole(10);
if(squareHole.fits(roundPeg)) {
console.log('The round peg fits into the square hole!');
} else {
const squarePegAdapter = new SquarePegAdapter(roundPeg);
if(squareHole.fits(squarePegAdapter)) {
console.log('The round peg fits into the square hole!');
} else {
console.log('The round peg does not fit into the square hole!');
}
}
在这个示例中,我们定义了一个需要适配的圆钉类 RoundPeg
和一个目标方孔类 SquareHole
。然后我们实现了一个适配器 SquarePegAdapter
,将圆钉适配成一个方形钉。在客户端代码中,我们先试图将圆钉放进方孔中。如果适配成功则输出 “The round peg fits into the square hole!”,否则我们使用 SquarePegAdapter
来适配圆钉,然后再试图将适配后的方钉放进方孔中。如果适配成功则输出 “The round peg fits into the square hole!”,否则输出 “The round peg does not fit into the square hole!”。
装饰器模式
装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许以动态的方式扩展对象的功能,同时还能保证不破坏对象原有的结构和功能。该模式使用方便的包装对象机制,就可以在运行时动态地添加、删除或更改对象的职责。
一个简单的装饰器模式的例子是:我们有一个餐厅,它提供各种餐点。我们可以为这些餐点添加各种配料,比如加入奶油、火腿等。这些配料不仅可以添加,还可以随时删除或更改。这就是装饰器模式的核心思想,它允许在运行时为对象添加功能,而不是在编译时。
下面是使用 JavaScript 实现的简单的装饰器模式的示例:
// 餐点类
class Meal {
constructor() {
this.price = 0;
}
getPrice() {
return this.price;
}
setPrice(price) {
this.price = price;
}
getDescription() {
return '';
}
}
// 配料装饰器类
class MealDecorator extends Meal {
constructor(meal) {
super();
this.meal = meal;
}
getPrice() {
return this.meal.getPrice() + this.price;
}
getDescription() {
return this.meal.getDescription() + this.description;
}
}
// 具体的餐点类
class Hamburger extends Meal {
constructor() {
super();
this.setPrice(10);
this.setDescription('Hamburger');
}
}
class Pizza extends Meal {
constructor() {
super();
this.setPrice(20);
this.setDescription('Pizza');
}
}
// 具体的配料装饰器类
class Cheese extends MealDecorator {
constructor(meal) {
super(meal);
this.setPrice(5);
this.description = ' + Cheese';
}
}
class Bacon extends MealDecorator {
constructor(meal) {
super(meal);
this.setPrice(8);
this.description = ' + Bacon';
}
}
// 客户端代码
let meal = new Hamburger();
console.log(meal.getDescription() + ' ' + meal.getPrice());
meal = new Cheese(meal);
console.log(meal.getDescription() + ' ' + meal.getPrice());
meal = new Bacon(meal);
console.log(meal.getDescription() + ' ' + meal.getPrice());
在这个示例中,我们定义了一个基础的餐点类 Meal
和一个装饰器类 MealDecorator
。然后我们又定义了两个具体的餐点类 Hamburger
和 Pizza
,以及具体的配料装饰器类 Cheese
和 Bacon
。这些具体的类分别根据自己的需要来实现餐点类或者装饰器类。
在客户端代码中,我们先创建一个汉堡餐 Hamburger
的实例,然后添加一份芝士和一份培根,最后输出餐点的描述以及价格。首先输出的是 “Hamburger 10”,然后再添加芝士之后输出 “Hamburger + Cheese 15”,最后再添加培根之后输出 “Hamburger + Cheese + Bacon 23”。这样,我们就成功地使用了装饰器模式来为餐点添加配料。
代理模式
代理模式(Proxy Pattern)是一种结构型设计模式,它提供了一种代理对象来控制对另一个对象的访问。代理对象充当着客户端与另一个对象之间的中介
,隐藏了另一个对象的复杂性
,并在不改变另一个对象的情况下,提供了一些额外的功能。
一个简单的代理模式的例子是:我们有一个图片加载类,它负责加载远程图片,并将其显示在页面中。但是,在某些情况下,我们不想直接将图片加载到页面中,而是想在用户浏览器中将它进行预加载,等到用户需要加载它时再将其显示在页面中。在这种情况下,我们可以使用代理模式来实现这一需求。
下面是使用 JavaScript 实现的简单的代理模式的示例:
// 图片加载类
class ImageLoader {
constructor(url) {
this.url = url;
this.image = null;
}
loadImage() {
if(!this.image) {
this.image = new Image();
this.image.src = this.url;
}
return this.image;
}
}
// 图片预加载代理类
class ImagePreloader {
constructor(url) {
this.imageLoader = new ImageLoader(url);
}
preloadImage() {
const image = this.imageLoader.loadImage();
if(!image.complete) {
image.addEventListener('load', (event) => {
this.displayImage();
});
} else {
this.displayImage();
}
}
displayImage() {
console.log('Image is ready to be displayed!');
// 在这里将图片显示到页面中
}
}
// 客户端代码
const imagePreloader = new ImagePreloader('http://www.example.com/image.jpg');
imagePreloader.preloadImage();
在这个示例中,我们定义了一个图片加载类 ImageLoader
,它负责加载图片并返回图片对象。然后我们又定义了一个图片预加载代理类 ImagePreloader
,它封装了 ImageLoader
类对象,并在图片没有完全加载时,使用 addEventListener()
方法监听 load
事件,等到图片加载完成后再将其显示到页面中。
在客户端代码中,我们创建了一个 ImagePreloader
类对象,并调用它的 preloadImage()
方法来预加载远程图片。当图片加载完成后,代理对象将自动调用 displayImage()
方法将其显示在页面中。这样,我们就成功地使用代理模式来延迟加载图片,提高了网页加载速度,提升了用户体验。
外观模式
外观模式(Facade Pattern)是一种结构型设计模式,它为复杂的子系统提供一个简单的接口,使得其易于使用。该模式隐藏了子系统的复杂性,让客户端只需要与一个简单的外观对象进行交互,而不用与子系统的每个组件进行交互,从而降低了客户端代码的复杂度和依赖性。
一个简单的外观模式的例子是:我们有一个DVD播放器,它由多个组件组成,包括电源、显示器、音频等。现在我们要设计一个外观对象,它可以控制DVD的开始、暂停和停止等操作。客户端只需要与这个简单的外观对象进行交互,而不用了解每个组件的具体实现细节。
下面是使用 JavaScript 实现的简单的外观模式的示例:
// DVD播放器电源组件类
class Power {
constructor() {
this.isTurnedOn = false;
}
turnOn() {
this.isTurnedOn = true;
console.log('Power is turned on');
}
turnOff() {
this.isTurnedOn = false;
console.log('Power is turned off');
}
}
// DVD播放器显示器组件类
class Display {
display(title) {
console.log(`Displaying ${title}`);
}
}
// DVD播放器音频组件类
class Audio {
play() {
console.log('Audio is playing');
}
pause() {
console.log('Audio is paused');
}
stop() {
console.log('Audio is stopped');
}
}
// DVD播放器外观类
class DVDPlayerFacade {
constructor() {
this.power = new Power();
this.display = new Display();
this.audio = new Audio();
}
play(title) {
this.power.turnOn();
this.display.display(title);
this.audio.play();
}
pause() {
this.audio.pause();
}
stop() {
this.audio.stop();
this.power.turnOff();
}
}
// 客户端代码
const dvdPlayer = new DVDPlayerFacade();
dvdPlayer.play('Star Wars: The Last Jedi');
dvdPlayer.pause();
dvdPlayer.stop();
在这个示例中,我们定义了一个DVD播放器的电源组件类 Power
、显示器组件类 Display
和音频组件类 Audio
。然后我们又定义了一个DVD播放器外观类 DVDPlayerFacade
,它将这些组件进行组合,提供了一个简单的接口,使得客户端可以控制DVD的开始、暂停和停止等操作。
在客户端代码中,我们创建了一个DVD播放器外观类对象 DVDPlayerFacade
,并分别调用它的 play()
、pause()
和 stop()
方法来控制DVD的播放操作。这些操作对于客户端代码来说是透明的,它只需要了解这个简单的外观接口,而不需要了解具体的组件实现细节。这样,我们就成功地使用外观模式来简化了DVD播放器的操作,提高了客户端代码的可读性和可维护性。
桥接模式
桥接模式(Bridge Pattern)是一种结构型设计模式,它允许你将抽象部分与实现部分分离,使得它们可以独立地变化。在桥接模式中,我们将一个对象的实现与其抽象进行分离,从而使得它们可以单独进行变化,而不会相互影响。
一个简单的桥接模式的例子是:我们有一个电视机和多个遥控器,它们之间的关系是一对多的关系。我们可以使用桥接模式来将这个关系进行抽象,从而让一个电视机对象可以与多个遥控器对象进行关联,并且可以随时切换遥控器,而不会影响电视机对象的状态和行为。
下面是使用 JavaScript 实现的简单的桥接模式的示例:
// 抽象遥控器类
class RemoteControl {
constructor(tv) {
this.tv = tv;
}
on() {
this.tv.on();
}
off() {
this.tv.off();
}
setChannel(channel) {
this.tv.setChannel(channel);
}
}
// 具体遥控器类
class BasicRemoteControl extends RemoteControl {
constructor(tv) {
super(tv);
}
}
class AdvancedRemoteControl extends RemoteControl {
constructor(tv) {
super(tv);
}
setVolume(volume) {
this.tv.setVolume(volume);
}
}
// 抽象电视机类
class TV {
constructor() {}
on() {}
off() {}
setChannel(channel) {}
}
// 具体电视机类
class SonyTV extends TV {
constructor() {
super();
this.channel = 1;
}
on() {
console.log('Sony TV is turned on');
}
off() {
console.log('Sony TV is turned off');
}
setChannel(channel) {
this.channel = channel;
console.log(`Sony TV channel is set to ${channel}`);
}
}
class SamsungTV extends TV {
constructor() {
super();
this.channel = 1;
}
on() {
console.log('Samsung TV is turned on');
}
off() {
console.log('Samsung TV is turned off');
}
setChannel(channel) {
this.channel = channel;
console.log(`Samsung TV channel is set to ${channel}`);
}
setVolume(volume) {
console.log(`Samsung TV volume is set to ${volume}`);
}
}
// 客户端代码
const sonyTV = new SonyTV();
const samsungTV = new SamsungTV();
const basicRemote = new BasicRemoteControl(sonyTV);
basicRemote.on();
basicRemote.setChannel(4);
basicRemote.off();
const advancedRemote = new AdvancedRemoteControl(samsungTV);
advancedRemote.on();
advancedRemote.setChannel(7);
advancedRemote.setVolume(15);
advancedRemote.off();
在这个示例中,定义了一个抽象遥控器类 RemoteControl
和一个抽象电视机类 TV
。
然后又定义了两个具体遥控器类 BasicRemoteControl
和 AdvancedRemoteControl
,以及两个具体电视机类 SonyTV
和 SamsungTV
。
组合模式
组合模式(Composite Pattern)是一种结构型设计模式,它能够将对象组合成树形结构,并且能像使用独立对象一样使用其中的任何一个对象。在组合模式中,我们可以将对象组合成树形结构以表示 “部分-整体” 的层次结构,同时还能让客户端以统一的方式对待单个对象和组合对象。
一个简单的组合模式的例子是:我们有一个组织机构,包括多个部门和员工。我们可以使用组合模式来表示这种 “部分-整体” 的层次结构,并且能够统一地对待部门和员工。
下面是使用 JavaScript 实现的简单的组合模式的示例:
// 抽象组件类
class OrganizationComponent {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
print() {}
}
// 具体部门类
class Department extends OrganizationComponent {
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);
}
}
print() {
console.log(`Department: ${this.getName()}`);
for(let child of this.children) {
child.print();
}
}
}
// 具体员工类
class Employee extends OrganizationComponent {
constructor(name) {
super(name);
}
print() {
console.log(`Employee: ${this.getName()}`);
}
}
// 客户端代码
const organization = new Department('Headquarter');
const financeDept = new Department('Finance Department');
financeDept.add(new Employee('Alice'));
financeDept.add(new Employee('Bob'));
const salesDept = new Department('Sales Department');
salesDept.add(new Employee('Charlie'));
salesDept.add(new Employee('David'));
organization.add(financeDept);
organization.add(salesDept);
organization.print();
在这个示例中,我们定义了一个抽象组件类 OrganizationComponent
和两个具体的类 Department
和 Employee
。具体部门类 Department
内部维护一个员工列表,同时还实现了添加、删除和打印子组件的方法。具体员工类 Employee
重写了 print()
方法用于输出员工姓名。
在客户端代码中,我们创建了一个总部部门 Headquarter
,然后又分别创建了财务部门 Finance Department
和销售部门 Sales Department
。其中,财务部门又包括了员工 Alice
和员工 Bob
;销售部门又包括了员工 Charlie
和员工 David
。
享元模式
享元模式(Flyweight Pattern)是一种结构型设计模式,它可以减少程序中的对象数量,通过共享相同对象的方式来减少内存和计算资源的开销。该模式的主要思路是将一个对象的状态拆分成内部状态和外部状态两部分,内部状态作为对象的固有属性进行共享复用,外部状态则通过参数的方式传递进来,每个独立对象可以重用内部状态,拥有自己独有的外部状态。
以下是JS中的一个享元模式的示例代码:
// 定义一个享元工厂对象
var FlyweightFactory = (function () {
var flyweights = {};
return {
get: function (key) {
if (!flyweights[key]) {
flyweights[key] = new Flyweight(key);
}
return flyweights[key];
},
getCount: function () {
var count = 0;
for (var f in flyweights) count++;
return count;
}
};
})();
// 定义一个享元对象
function Flyweight (name) {
this.name = name;
}
Flyweight.prototype.execute = function (state) {
console.log(`Flyweight ${this.name}, state: ${state}`);
};
// 定义一个客户端对象
function Client (name, state) {
this.flyweight = FlyweightFactory.get(name);
this.state = state;
}
Client.prototype.execute = function () {
this.flyweight.execute(this.state);
};
// 客户端调用
var client1 = new Client('flyweight1', 'state1');
client1.execute(); // Flyweight flyweight1, state: state1
var client2 = new Client('flyweight2', 'state2');
client2.execute(); // Flyweight flyweight2, state: state2
console.log(`共创建了 ${FlyweightFactory.getCount()} 个享元对象`);
在上述代码中,我们通过工厂对象FlyweightFactory创建享元对象Flyweight,并通过客户端对象Client在需要使用时获取享元对象。由于享元对象Flyweight的内部状态是可以共享的,因此我们只需要在需要使用时获取相应的享元对象,而不需要多次创建。这样可以节省内存和计算资源的开销。