Javascript设计模式 - 《代理模式(结构型)》
概念
代理模式(Proxy Pattern)是一种结构型设计模式,它允许你创建一个代理对象,用来控制对另一个对象的访问
代理模式通过引入一个代理对象来代替原始对象,实现了对原始对象的间接访问、控制或者增强
代理对象拥有与原始对象相同的接口,使得客户端无需知道代理对象的存在
在代理模式中,存在两个关键角色:真实主题(Real Subject)和代理主题(Proxy Subject)
真实主题是客户端直接访问的对象,而代理主题是客户端通过代理间接访问真实主题的对象
通过代理模式,可以实现多种功能,比如:
远程代理(Remote Proxy)
代理对象可以代表远程的对象,允许客户端通过代理对象访问远程的资源或服务,而无需了解远程对象的具体细节
虚拟代理(Virtual Proxy)
代理对象可以延迟加载真实对象,只有当客户端需要访问真实对象时才进行加载,从而提高系统的性能和资源利用率
保护代理(Protective Proxy)
代理对象可以控制对真实对象的访问权限,限制客户端对真实对象的直接访问,增强系统的安全性
缓存代理(Cache Proxy)
代理对象可以缓存真实对象的结果,当客户端再次请求相同的操作时,可以直接返回缓存的结果,减少重复计算,提高系统的性能
代理模式的核心思想是通过引入代理对象来控制对真实对象的访问,从而实现对真实对象的间接访问、控制或者增强
这种分离使得系统更加灵活,能够更好地应对需求变化,同时也提高了系统的安全性和性能
举个简单的例子,考虑一个网络请求的程序
可以有不同类型的网络请求(如GET请求、POST请求等),也可以有不同的处理方式(如异步处理、同步处理等)
使用代理模式,可以将网络请求的类型和处理方式分离开来,使得可以很容易地添加新的网络请求类型或处理方式,而不需要修改已有的代码
实现条件
目标对象
代理模式需要有一个目标对象,它是需要被代理的对象
目标对象定义了要被代理的功能和行为
代理对象
代理对象是一个与目标对象具有相同接口的对象
代理对象在客户端和目标对象之间起到中间人的作用,通过拦截对目标对象的访问,可以对目标对象的行为进行控制、调整或增强
一致的接口
代理对象和目标对象通常应具有相同的接口
这意味着代理对象可以接受和处理客户端对目标对象的所有请求
客户端通过使用代理对象而不是直接使用目标对象,可以完全透明地调用目标对象的功能
处理程序
代理对象通常包含一个处理程序(handler),它定义了如何拦截并处理客户端对目标对象的请求
这可能包括操作前后的额外逻辑、权限控制、日志记录、数据验证等
拦截机制
代理对象需要能够拦截对目标对象的操作
这可以通过 JavaScript 中的
Proxy
对象来实现,Proxy
对象提供了一系列拦截器(如get
、set
、apply
等)来处理属性访问和函数调用等操作透明性
代理模式应保持对客户端的透明性,这意味着客户端不需要知道它正在与代理对象交互而不是目标对象
这使得客户端可以专注于业务逻辑而不必关心代理的细节
控制访问和增强
代理模式的核心是通过代理对象控制和增强对目标对象的访问
代理对象可以选择性地允许或拒绝客户端对目标对象的请求,或者在请求前后添加额外的逻辑
优点
分离关注点
代理模式使目标对象和客户端之间的逻辑分离
这可以使目标对象的实现更加专注于其核心功能,而代理负责其他方面的逻辑(例如,验证、权限控制、缓存、日志记录等)
增强对象功能
通过代理对象,可以在不修改目标对象的情况下增强其功能
例如,可以在目标对象的操作前后添加额外的行为
控制访问
代理模式可以控制对目标对象的访问
代理可以决定是否允许访问目标对象的特定功能,并在必要时进行权限验证或日志记录
延迟初始化
通过代理模式,可以将目标对象的初始化延迟到实际需要的时候
这有助于优化性能,避免不必要的资源消耗
安全性
代理模式可以用于提高系统的安全性
例如,通过代理对象,可以在调用目标对象的方法之前进行权限验证,确保只有授权的用户才能访问目标对象的功能
透明性
代理对象通常提供与目标对象相同的接口,因此客户端不需要了解目标对象是否被代理
这样,客户端的代码可以保持简单和一致
缺点
额外的复杂性
引入代理对象可能会增加代码的复杂性,因为需要管理额外的对象和逻辑
特别是当代理对象和目标对象之间的逻辑较复杂时,这种复杂性会更明显
性能开销
代理模式可能引入一定的性能开销
例如,代理对象在执行操作时需要额外的拦截和逻辑处理,这可能会影响系统的性能
调试难度
由于代理对象在目标对象和客户端之间起到中间层的作用,这可能使调试变得更复杂,因为问题可能出现在代理对象、目标对象或二者之间的交互中
维护难度
代理模式可能导致代码维护难度增加,尤其是当代理对象和目标对象之间的逻辑复杂时
在设计代理对象时需要特别注意清晰的接口定义和职责划分
实现方式
ES6 Proxy
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// 定义目标对象
// 目标对象是需要被代理的对象,包括属性和方法
const target = {
name: "Alice",
age: 25,
greet() {
// greet 方法用于打印一个问候语
console.log(`Hello, my name is ${this.name}`);
},
};
// 定义处理程序对象
// 处理程序对象包含了代理对象的行为拦截逻辑
const handler = {
// 拦截对目标对象属性的读取操作
get(target, property, receiver) {
console.log(`Getting property '${property}'`);
// 使用 Reflect.get 方法获取目标对象的属性值
// 你可以在这里添加额外的逻辑,例如权限控制、日志记录等
return Reflect.get(target, property, receiver);
},
// 拦截对目标对象属性的设置操作
set(target, property, value, receiver) {
console.log(`Setting property '${property}' to '${value}'`);
// 使用 Reflect.set 方法设置目标对象的属性值
// 你可以在这里添加额外的逻辑,例如数据验证、权限控制等
return Reflect.set(target, property, value, receiver);
},
// 拦截对目标对象方法的调用操作
apply(target, thisArg, argumentsList) {
console.log(`Calling function with arguments: ${argumentsList}`);
// 使用 Reflect.apply 方法调用目标对象的方法
// 你可以在这里添加额外的逻辑,例如日志记录、权限控制等
return Reflect.apply(target, thisArg, argumentsList);
},
};
// 使用 Proxy 构造函数创建代理对象
// 参数为目标对象和处理程序对象
const proxy = new Proxy(target, handler);
// 访问代理对象的 name 属性
// 代理对象会调用 handler.get 拦截器
console.log(proxy.name); // 获取 name 属性,拦截器将打印日志
// 设置代理对象的 age 属性
// 代理对象会调用 handler.set 拦截器
proxy.age = 30; // 设置 age 属性,拦截器将打印日志
// 调用代理对象的 greet 方法
// 代理对象会调用 handler.apply 拦截器
proxy.greet(); // 调用 greet 方法,拦截器将打印日志Class
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// 定义一个目标对象
const target = {
name: "Alice",
age: 25,
greet() {
console.log(`Hello, my name is ${this.name}`);
}
};
// 定义一个代理类
class ProxyHandler {
constructor(target) {
this.target = target;
}
// 拦截对目标对象的属性读取
get(property) {
console.log(`Getting property '${property}'`);
if (property in this.target) {
return this.target[property];
} else {
console.error(`Property '${property}' does not exist on target object.`);
return undefined;
}
}
// 拦截对目标对象的属性设置
set(property, value) {
console.log(`Setting property '${property}' to '${value}'`);
if (property in this.target) {
this.target[property] = value;
return true;
} else {
console.error(`Property '${property}' does not exist on target object.`);
return false;
}
}
// 调用目标对象的方法
apply(methodName, ...args) {
console.log(`Calling method '${methodName}' with arguments: ${args}`);
const method = this.target[methodName];
if (typeof method === 'function') {
return method.apply(this.target, args);
} else {
console.error(`Method '${methodName}' does not exist on target object.`);
return undefined;
}
}
}
// 使用代理类来创建代理对象
const handler = new ProxyHandler(target);
// 使用代理对象
console.log(handler.get('name')); // 获取 name 属性,拦截器将打印日志
handler.set('age', 30); // 设置 age 属性,拦截器将打印日志
handler.apply('greet'); // 调用 greet 方法,拦截器将打印日志
场景
Vue.js
Vue 使用代理模式来实现数据绑定和响应式编程
Vue 创建了一个代理对象,通过代理对象监视数据的变化,然后根据变化自动更新 UI
Redux 中的 Middleware
虽然 Redux 本身是一个状态管理库,但在中间件(middleware)的设计中有使用代理模式
中间件可以拦截并调整 Redux 的动作,这样可以在动作被派发和状态更新之间添加额外的逻辑,如日志记录、异步请求等
Node.js 的 HTTP 代理
在 Node.js 中,开发人员可以使用库如
http-proxy
来创建 HTTP 代理这个代理模式允许开发人员在客户端和服务器之间创建一个中间层,通过该层可以调整 HTTP 请求和响应
例如,可以修改请求头、缓存请求或将请求重定向到其他服务器
GraphQL 中的 Resolvers
在 GraphQL 中,Resolvers 是用于处理 GraphQL 查询的函数
通过代理模式,可以在查询结果返回客户端之前,对结果进行拦截和调整
例如,添加额外的数据处理、权限控制等
Angular
Angular 框架中的依赖注入系统使用了代理模式
通过拦截服务的创建和调用,Angular 可以实现服务的生命周期管理、配置和注入依赖等
jQuery
虽然 jQuery 不直接使用原生的 JavaScript
Proxy
对象,但 jQuery 中的一些功能,例如事件代理(event delegation),与代理模式的理念相似通过事件代理,jQuery 可以在父元素上监听事件,然后根据事件的目标元素来执行特定的处理