代理模式详解:概念、实现与应用
引言
代理模式是一种结构型设计模式,它能让你提供对象的替代品或其占位符。代理控制着对于原对象的访问,并允许在将请求提交给对象前后进行一些处理。
什么是代理模式?
代理模式是一种结构型设计模式,它能让你提供对象的替代品或其占位符。代理控制着对于原对象的访问,并允许在将请求提交给对象前后进行一些处理。代理模式通过创建一个代理对象来控制对真实对象的访问。
核心思想
代理模式的核心思想是:
- 访问控制:控制对真实对象的访问
- 延迟初始化:延迟创建开销大的对象
- 增强功能:在访问真实对象前后添加额外功能
为什么需要代理模式?
在许多情况下,我们需要在访问对象时添加额外的控制:
1. 远程代理
当对象位于不同的地址空间或网络中时:
- 隐藏网络通信的复杂性
- 提供本地对象接口访问远程对象
- 处理网络错误和重试
2. 虚拟代理
当对象创建开销很大时:
- 延迟创建昂贵的对象
- 在需要时才加载资源
- 提供占位符对象
3. 保护代理
当需要控制对象的访问权限时:
- 基于用户角色控制访问
- 实现细粒度的权限控制
- 记录访问日志
代理模式的基本实现
让我们从一个简单的代理模式实现开始:
javascript
// 主题接口
class Subject {
request() {
throw new Error('必须实现 request 方法');
}
}
// 真实主题
class RealSubject extends Subject {
request() {
return '真实主题:处理请求';
}
}
// 代理
class Proxy extends Subject {
constructor(realSubject) {
super();
this.realSubject = realSubject;
}
request() {
if (this.checkAccess()) {
const result = this.realSubject.request();
this.logAccess();
return result;
}
return '代理:访问被拒绝';
}
checkAccess() {
console.log('代理:检查访问权限');
return true;
}
logAccess() {
console.log('代理:记录访问日志');
}
}
// 客户端代码
function clientCode(subject) {
console.log(subject.request());
}
console.log('客户端:直接执行真实主题:');
const realSubject = new RealSubject();
clientCode(realSubject);
console.log('');
console.log('客户端:通过代理执行:');
const proxy = new Proxy(realSubject);
clientCode(proxy);实现要点分析
- 主题接口:定义真实对象和代理对象的公共接口
- 真实主题:实现实际业务逻辑的对象
- 代理:实现与真实主题相同的接口,控制对真实主题的访问
- 客户端:通过主题接口与对象交互
代理模式的实际应用场景
1. 图像加载代理
在图形应用中,代理模式非常适合用于延迟加载大图像:
javascript
// 图像接口
class Image {
display() {
throw new Error('必须实现 display 方法');
}
}
// 真实图像
class RealImage extends Image {
constructor(filename) {
super();
this.filename = filename;
this.loadImageFromDisk();
}
loadImageFromDisk() {
console.log(`从磁盘加载图像: ${this.filename}`);
// 模拟耗时的图像加载过程
console.log('图像加载完成');
}
display() {
return `显示图像: ${this.filename}`;
}
}
// 图像代理
class ImageProxy extends Image {
constructor(filename) {
super();
this.filename = filename;
this.realImage = null;
}
display() {
if (!this.realImage) {
console.log('图像代理:首次访问,创建真实图像对象');
this.realImage = new RealImage(this.filename);
}
return this.realImage.display();
}
}
// 图像浏览器
class ImageViewer {
constructor() {
this.images = new Map();
}
loadImage(filename) {
if (!this.images.has(filename)) {
console.log(`图像浏览器:注册图像 ${filename}`);
this.images.set(filename, new ImageProxy(filename));
}
return this.images.get(filename);
}
displayImage(filename) {
const image = this.loadImage(filename);
console.log(image.display());
}
}
// 使用示例
function imageProxyExample() {
const viewer = new ImageViewer();
console.log('=== 注册图像 ===');
viewer.loadImage('photo1.jpg');
viewer.loadImage('photo2.jpg');
viewer.loadImage('photo3.jpg');
console.log('\n=== 显示图像 ===');
viewer.displayImage('photo1.jpg');
console.log('');
viewer.displayImage('photo2.jpg');
console.log('');
viewer.displayImage('photo1.jpg'); // 再次显示,不会重新加载
}
imageProxyExample();2. API 请求代理
在 Web 应用中,代理模式非常适合用于缓存和控制 API 请求:
javascript
// API 客户端接口
class APIClient {
async get(url) {
throw new Error('必须实现 get 方法');
}
async post(url, data) {
throw new Error('必须实现 post 方法');
}
}
// 真实 API 客户端
class RealAPIClient extends APIClient {
async get(url) {
console.log(`真实客户端:发送 GET 请求到 ${url}`);
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 1000));
// 模拟 API 响应
return {
status: 200,
data: { message: `GET 请求响应 from ${url}`, timestamp: Date.now() }
};
}
async post(url, data) {
console.log(`真实客户端:发送 POST 请求到 ${url},数据: ${JSON.stringify(data)}`);
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 1000));
// 模拟 API 响应
return {
status: 201,
data: { message: `POST 请求响应 from ${url}`, timestamp: Date.now() }
};
}
}
// API 代理
class APIProxy extends APIClient {
constructor(realClient, cache = new Map()) {
super();
this.realClient = realClient;
this.cache = cache;
this.requestCount = 0;
}
async get(url) {
this.requestCount++;
console.log(`API 代理:处理第 ${this.requestCount} 个请求`);
// 检查缓存
if (this.cache.has(url)) {
console.log(`API 代理:从缓存返回数据 for ${url}`);
return this.cache.get(url);
}
// 检查访问频率
if (!this.checkRateLimit()) {
throw new Error('API 代理:请求频率过高');
}
// 检查权限
if (!this.checkPermission('read')) {
throw new Error('API 代理:读取权限不足');
}
console.log(`API 代理:转发请求到真实客户端`);
const response = await this.realClient.get(url);
// 缓存响应(仅对 GET 请求)
this.cache.set(url, response);
// 记录日志
this.logRequest('GET', url);
return response;
}
async post(url, data) {
this.requestCount++;
console.log(`API 代理:处理第 ${this.requestCount} 个请求`);
// 检查访问频率
if (!this.checkRateLimit()) {
throw new Error('API 代理:请求频率过高');
}
// 检查权限
if (!this.checkPermission('write')) {
throw new Error('API 代理:写入权限不足');
}
console.log(`API 代理:转发请求到真实客户端`);
const response = await this.realClient.post(url, data);
// 清除相关缓存
this.clearCacheForUrl(url);
// 记录日志
this.logRequest('POST', url, data);
return response;
}
checkRateLimit() {
// 简单的频率限制:每秒最多 5 个请求
if (this.requestCount > 5) {
console.log('API 代理:请求频率过高');
return false;
}
return true;
}
checkPermission(operation) {
// 简单的权限检查
const permissions = ['read', 'write'];
return permissions.includes(operation);
}
logRequest(method, url, data = null) {
console.log(`API 代理:记录请求日志 - ${method} ${url}${data ? ` with data: ${JSON.stringify(data)}` : ''}`);
}
clearCacheForUrl(url) {
// 清除与该 URL 相关的缓存
for (const key of this.cache.keys()) {
if (key.includes(url)) {
this.cache.delete(key);
}
}
}
getCacheStats() {
return {
cacheSize: this.cache.size,
requestCount: this.requestCount
};
}
}
// API 管理器
class APIManager {
constructor() {
this.clients = new Map();
this.cache = new Map();
}
registerClient(name, client) {
const proxy = new APIProxy(client, this.cache);
this.clients.set(name, proxy);
}
getClient(name) {
const client = this.clients.get(name);
if (!client) {
throw new Error(`未找到 API 客户端: ${name}`);
}
return client;
}
async request(clientName, method, url, data = null) {
const client = this.getClient(clientName);
try {
let response;
switch (method.toLowerCase()) {
case 'get':
response = await client.get(url);
break;
case 'post':
response = await client.post(url, data);
break;
default:
throw new Error(`不支持的 HTTP 方法: ${method}`);
}
console.log(`API 请求成功: ${method} ${url}`);
return response;
} catch (error) {
console.error(`API 请求失败: ${method} ${url}`, error.message);
throw error;
}
}
getStats(clientName) {
const client = this.getClient(clientName);
return client.getCacheStats();
}
}
// 使用示例
async function apiProxyExample() {
const apiManager = new APIManager();
// 注册 API 客户端
apiManager.registerClient('main', new RealAPIClient());
console.log('=== 发送 GET 请求 ===');
try {
const response1 = await apiManager.request('main', 'GET', '/api/users');
console.log('响应:', response1);
// 再次请求相同 URL,应该从缓存获取
const response2 = await apiManager.request('main', 'GET', '/api/users');
console.log('缓存响应:', response2);
} catch (error) {
console.error('GET 请求失败:', error.message);
}
console.log('\n=== 发送 POST 请求 ===');
try {
const response = await apiManager.request('main', 'POST', '/api/users', { name: '张三', email: 'zhangsan@example.com' });
console.log('响应:', response);
} catch (error) {
console.error('POST 请求失败:', error.message);
}
console.log('\n=== 获取统计信息 ===');
const stats = apiManager.getStats('main');
console.log('统计信息:', stats);
}
// 运行示例
apiProxyExample();3. 数据库连接代理
在数据库访问中,代理模式非常适合用于连接池管理和权限控制:
javascript
// 数据库连接接口
class DatabaseConnection {
query(sql) {
throw new Error('必须实现 query 方法');
}
execute(sql) {
throw new Error('必须实现 execute 方法');
}
close() {
throw new Error('必须实现 close 方法');
}
}
// 真实数据库连接
class RealDatabaseConnection extends DatabaseConnection {
constructor(connectionId) {
super();
this.connectionId = connectionId;
this.isConnected = true;
console.log(`真实连接:创建数据库连接 ${this.connectionId}`);
}
query(sql) {
if (!this.isConnected) {
throw new Error('连接已关闭');
}
console.log(`真实连接 ${this.connectionId}:执行查询 "${sql}"`);
// 模拟查询结果
return {
rows: [
{ id: 1, name: '用户1', email: 'user1@example.com' },
{ id: 2, name: '用户2', email: 'user2@example.com' }
],
rowCount: 2
};
}
execute(sql) {
if (!this.isConnected) {
throw new Error('连接已关闭');
}
console.log(`真实连接 ${this.connectionId}:执行更新 "${sql}"`);
// 模拟执行结果
return { affectedRows: 1 };
}
close() {
if (this.isConnected) {
console.log(`真实连接 ${this.connectionId}:关闭数据库连接`);
this.isConnected = false;
}
}
}
// 数据库连接代理
class DatabaseConnectionProxy extends DatabaseConnection {
constructor(realConnection, userRole) {
super();
this.realConnection = realConnection;
this.userRole = userRole;
this.queryCount = 0;
this.executeCount = 0;
}
query(sql) {
this.queryCount++;
// 检查权限
if (!this.checkQueryPermission(sql)) {
throw new Error('查询权限不足');
}
// 检查查询频率
if (!this.checkRateLimit()) {
throw new Error('查询频率过高');
}
// 记录查询日志
this.logQuery(sql);
// 执行真实查询
return this.realConnection.query(sql);
}
execute(sql) {
this.executeCount++;
// 检查权限
if (!this.checkExecutePermission(sql)) {
throw new Error('执行权限不足');
}
// 检查执行频率
if (!this.checkRateLimit()) {
throw new Error('执行频率过高');
}
// 记录执行日志
this.logExecute(sql);
// 执行真实操作
return this.realConnection.execute(sql);
}
close() {
console.log(`连接代理:关闭连接,查询次数: ${this.queryCount}, 执行次数: ${this.executeCount}`);
this.realConnection.close();
}
checkQueryPermission(sql) {
const lowerSql = sql.toLowerCase();
// 管理员可以执行所有查询
if (this.userRole === 'admin') {
return true;
}
// 普通用户只能执行 SELECT 查询
if (this.userRole === 'user' && lowerSql.trim().startsWith('select')) {
return true;
}
return false;
}
checkExecutePermission(sql) {
const lowerSql = sql.toLowerCase();
// 只有管理员可以执行修改操作
if (this.userRole === 'admin') {
return true;
}
// 普通用户不能执行修改操作
if (this.userRole === 'user') {
return false;
}
return false;
}
checkRateLimit() {
const totalOperations = this.queryCount + this.executeCount;
// 简单的频率限制:每个连接最多 100 次操作
if (totalOperations > 100) {
console.log('连接代理:操作频率过高');
return false;
}
return true;
}
logQuery(sql) {
console.log(`连接代理:记录查询日志 - 用户: ${this.userRole}, SQL: ${sql}`);
}
logExecute(sql) {
console.log(`连接代理:记录执行日志 - 用户: ${this.userRole}, SQL: ${sql}`);
}
getStats() {
return {
queryCount: this.queryCount,
executeCount: this.executeCount,
userRole: this.userRole
};
}
}
// 数据库连接池
class DatabaseConnectionPool {
constructor() {
this.connections = new Map();
this.connectionCounter = 0;
}
getConnection(userRole = 'user') {
this.connectionCounter++;
const connectionId = `conn_${this.connectionCounter}`;
const realConnection = new RealDatabaseConnection(connectionId);
const proxyConnection = new DatabaseConnectionProxy(realConnection, userRole);
this.connections.set(connectionId, proxyConnection);
return proxyConnection;
}
releaseConnection(connection) {
// 在实际实现中,这里会将连接返回到池中
// 为了简化,我们直接关闭连接
connection.close();
}
getStats() {
const stats = {};
for (const [id, connection] of this.connections) {
stats[id] = connection.getStats();
}
return stats;
}
}
// 使用示例
function databaseProxyExample() {
const connectionPool = new DatabaseConnectionPool();
console.log('=== 普通用户连接 ===');
const userConnection = connectionPool.getConnection('user');
try {
// 普通用户可以执行查询
const users = userConnection.query('SELECT * FROM users');
console.log('查询结果:', users);
// 普通用户不能执行更新操作
try {
userConnection.execute('UPDATE users SET name = "新名字" WHERE id = 1');
} catch (error) {
console.error('执行失败:', error.message);
}
} catch (error) {
console.error('操作失败:', error.message);
}
console.log('\n=== 管理员连接 ===');
const adminConnection = connectionPool.getConnection('admin');
try {
// 管理员可以执行查询
const users = adminConnection.query('SELECT * FROM users');
console.log('查询结果:', users);
// 管理员可以执行更新操作
const result = adminConnection.execute('UPDATE users SET name = "新名字" WHERE id = 1');
console.log('更新结果:', result);
} catch (error) {
console.error('操作失败:', error.message);
}
console.log('\n=== 释放连接 ===');
connectionPool.releaseConnection(userConnection);
connectionPool.releaseConnection(adminConnection);
console.log('\n=== 连接池统计 ===');
const stats = connectionPool.getStats();
console.log('统计信息:', JSON.stringify(stats, null, 2));
}
databaseProxyExample();代理模式的三种主要类型
1. 远程代理
远程代理为不同地址空间中的对象提供本地代表:
javascript
// 远程服务接口
class RemoteService {
async processData(data) {
throw new Error('必须实现 processData 方法');
}
}
// 远程服务实现
class RealRemoteService extends RemoteService {
async processData(data) {
console.log('真实远程服务:处理数据');
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 2000));
return { result: `处理完成: ${data}`, timestamp: Date.now() };
}
}
// 远程代理
class RemoteProxy extends RemoteService {
constructor(remoteServiceUrl) {
super();
this.remoteServiceUrl = remoteServiceUrl;
this.realService = null;
}
async processData(data) {
if (!this.realService) {
console.log('远程代理:初始化远程服务连接');
this.realService = new RealRemoteService();
}
try {
console.log('远程代理:转发请求到远程服务');
const result = await this.realService.processData(data);
return result;
} catch (error) {
console.error('远程代理:远程服务调用失败', error.message);
throw new Error(`远程服务错误: ${error.message}`);
}
}
}2. 虚拟代理
虚拟代理根据需要创建开销大的对象:
javascript
// 图像接口
class Image {
display() {
throw new Error('必须实现 display 方法');
}
}
// 大图像实现
class HeavyImage extends Image {
constructor(filename) {
super();
this.filename = filename;
this.loadImage(); // 昂贵的操作
}
loadImage() {
console.log(`加载大图像: ${this.filename}`);
// 模拟昂贵的加载过程
console.log('图像加载完成');
}
display() {
return `显示大图像: ${this.filename}`;
}
}
// 虚拟代理
class VirtualImageProxy extends Image {
constructor(filename) {
super();
this.filename = filename;
this.realImage = null;
}
display() {
if (!this.realImage) {
console.log('虚拟代理:按需创建真实图像');
this.realImage = new HeavyImage(this.filename);
}
return this.realImage.display();
}
}3. 保护代理
保护代理控制对敏感对象的访问:
javascript
// 文档接口
class Document {
read() {
throw new Error('必须实现 read 方法');
}
write(content) {
throw new Error('必须实现 write 方法');
}
}
// 真实文档
class RealDocument extends Document {
constructor(title, content) {
super();
this.title = title;
this.content = content;
}
read() {
return this.content;
}
write(content) {
this.content = content;
return '文档已更新';
}
}
// 保护代理
class ProtectionProxy extends Document {
constructor(realDocument, userRole) {
super();
this.realDocument = realDocument;
this.userRole = userRole;
}
read() {
console.log(`保护代理:用户 ${this.userRole} 尝试读取文档`);
return this.realDocument.read();
}
write(content) {
if (this.userRole !== 'admin') {
throw new Error('写入权限不足');
}
console.log(`保护代理:管理员 ${this.userRole} 尝试写入文档`);
return this.realDocument.write(content);
}
}代理模式与其它模式的对比
代理模式 vs 装饰器模式
javascript
// 代理模式 - 控制访问
class Proxy {
request() {
if (this.checkAccess()) {
return this.realSubject.request();
}
return '访问被拒绝';
}
}
// 装饰器模式 - 增强功能
class Decorator {
operation() {
// 增强原有功能
console.log('添加额外功能');
return this.component.operation();
}
}代理模式 vs 适配器模式
javascript
// 代理模式 - 控制对相同接口的访问
class Proxy {
request() {
// 控制访问
return this.realSubject.request();
}
}
// 适配器模式 - 转换不同接口
class Adapter {
request() {
// 转换接口
return this.adaptee.specificRequest();
}
}代理模式的优缺点
优点
- 访问控制:可以控制对服务对象的访问
- 延迟初始化:可以延迟创建开销大的对象
- 增强功能:可以在访问对象前后添加额外功能
- 透明性:客户端无需修改代码即可使用代理
- 可复用性:代理可以在不同对象间复用
缺点
- 复杂性增加:引入代理会增加代码复杂性
- 性能开销:代理可能带来额外的性能开销
- 响应时间:可能增加服务响应时间
- 调试困难:多层代理可能使调试变得困难
总结
代理模式是一种结构型设计模式,它能让你提供对象的替代品或其占位符。代理控制着对于原对象的访问,并允许在将请求提交给对象前后进行一些处理。
通过本章的学习,我们了解了:
- 代理模式的基本概念和核心思想
- 代理模式的三种主要类型(远程代理、虚拟代理、保护代理)
- 代理模式在实际开发中的应用场景(图像加载、API请求、数据库连接)
- 代理模式与其他结构型模式的对比
- 代理模式的优缺点
代理模式在现代软件开发中应用广泛,特别是在需要控制对象访问、延迟初始化或增强功能的场景中,它可以很好地支持系统的安全性和性能。
在下一章中,我们将继续探讨其他结构型模式,首先是外观模式。