零基础设计模式——结构型模式 - 代理模式

news2025/5/31 20:48:23

第三部分:结构型模式 - 代理模式 (Proxy Pattern)

在学习了享元模式如何通过共享对象来优化资源使用后,我们来探讨结构型模式的最后一个模式——代理模式。代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。

  • 核心思想:为其他对象提供一种代理以控制对这个对象的访问。

代理模式 (Proxy Pattern)

“为其他对象提供一种代理以控制对这个对象的访问。” (Provide a surrogate or placeholder for another object to control access to it.)

想象一下,你想看一张非常大的高清图片,但加载它需要很长时间。或者,你可能需要访问一个远程服务器上的资源,网络延迟很高。或者,你可能需要对某个操作进行权限检查,只有特定用户才能执行。

代理模式通过引入一个代理对象来间接访问真实对象(也称为主题对象或服务对象)。客户端与代理对象交互,代理对象再根据需要与真实对象交互。

  • 真实主题 (Real Subject):实际执行任务的对象,如大图片加载器、远程服务接口、需要权限的操作。
  • 代理 (Proxy):控制对真实主题访问的替身。它可以实现与真实主题相同的接口,使得客户端可以无缝切换。

1. 目的 (Intent)

代理模式的主要目的:

  1. 控制访问:代理可以控制客户端对真实对象的访问权限、时机或方式。
  2. 提供间接层:在客户端和真实对象之间引入一个间接层,可以在这个层面上执行额外的操作,如延迟加载、缓存、日志记录、权限验证等。
  3. 简化复杂性:代理可以隐藏真实对象的复杂性,例如远程调用的网络细节。

2. 生活中的例子 (Real-world Analogy)

  • 信用卡

    • 你的银行账户(真实主题)里有钱。
    • 信用卡(代理)是你访问银行账户资金的一种方式。当你刷卡时,信用卡公司会验证你的身份、检查账户余额(控制访问),然后才允许交易。
  • 经纪人/中介

    • 你想买卖股票(真实主题是股票交易所)。
    • 你通过股票经纪人(代理)进行操作。经纪人会处理交易的细节,你不需要直接与交易所打交道。
    • 房产中介(代理)帮助你买卖房屋(真实主题是房产本身和房主)。
  • 门禁系统

    • 大楼的某个区域(真实主题)是受限的。
    • 门禁卡或保安(代理)验证你的身份和权限,决定是否允许你进入。
  • 明星的经纪人

    • 明星(真实主题)很忙。
    • 经纪人(代理)处理明星的日程安排、商业洽谈等,过滤掉不必要的干扰,并代表明星处理事务。

3. 结构 (Structure)

代理模式通常包含以下角色:

  1. Subject (主题接口):定义了 RealSubject 和 Proxy 的共同接口。这样,在任何使用 RealSubject 的地方都可以使用 Proxy。
  2. RealSubject (真实主题):定义了 Proxy 所代表的真实实体。这是实际执行业务逻辑的对象。
  3. Proxy (代理类)
    • 保存一个引用使得代理可以访问实体。若 RealSubject 和 Subject 的接口相同,Proxy 会引用 Subject。
    • 提供一个与 Subject 的接口相同的接口,这样代理就可以用来替代实体。
    • 控制对实体的存取,并可能负责创建和删除它。
    • 其他功能依赖于代理的类型。
  4. Client (客户端):通过 Subject 接口与 RealSubject 或 Proxy 交互。
    在这里插入图片描述
    工作流程
  • 客户端请求操作时,它会调用 Proxy 对象的方法。
  • Proxy 对象可能会执行一些预处理操作(如权限检查、日志记录)。
  • 如果需要,Proxy 会创建或获取 RealSubject 对象的引用,并将请求委托给 RealSubject
  • RealSubject 执行实际的操作。
  • Proxy 可能会执行一些后处理操作(如结果缓存、日志记录),然后将结果返回给客户端。

4. 常见代理类型 (Types of Proxies)

根据代理的目的和实现方式,有几种常见的代理类型:

  1. 虚拟代理 (Virtual Proxy)

    • 目的:延迟加载昂贵的对象。当创建真实对象的开销很大时,虚拟代理会推迟真实对象的创建,直到客户端真正需要它为止。
    • 例子:显示一个包含大量图片的文档,图片对象(真实主题)可以在实际滚动到屏幕上时才由虚拟代理创建和加载。
  2. 远程代理 (Remote Proxy)

    • 目的:为位于不同地址空间(如另一台机器上)的对象提供本地代表。远程代理负责处理网络通信的细节(如序列化、连接管理),使得客户端感觉像在调用本地对象一样。
    • 例子:Java RMI (Remote Method Invocation) 中的 Stub 对象就是远程代理。
  3. 保护代理 (Protection Proxy)

    • 目的:控制对真实对象的访问权限。在调用真实对象的方法之前,保护代理会检查客户端是否具有相应的权限。
    • 例子:根据用户角色控制对某些敏感操作的访问。
  4. 智能引用代理 (Smart Reference / Smart Proxy)

    • 目的:在访问对象时执行一些额外的操作,如引用计数、加锁以控制并发访问、对象加载时记录日志等。
    • 例子:C++ 中的智能指针(如 std::shared_ptr)可以看作是一种智能引用代理,负责管理对象的生命周期。
  5. 缓存代理 (Caching Proxy)

    • 目的:为开销大的操作结果提供临时存储。当多个客户端请求相同的结果时,可以直接从缓存中返回,避免重复计算或请求。
    • 例子:Web 代理服务器缓存常用网页;应用程序缓存数据库查询结果。
  6. 日志代理 (Logging Proxy)

    • 目的:在方法调用前后记录日志信息。

5. 适用场景 (When to Use)

  • 当你需要延迟初始化一个开销很大的对象时(虚拟代理)。
  • 当你需要控制对一个对象的访问权限时(保护代理)。
  • 当你需要为一个远程对象提供本地代表时(远程代理)。
  • 当你需要在访问对象时执行一些附加操作,如日志记录、缓存、事务管理等(智能引用代理、缓存代理、日志代理)。
  • 当你希望为一个对象提供不同级别的访问权限时。

6. 优缺点 (Pros and Cons)

优点:

  1. 控制访问:代理模式的核心优势在于能够控制对真实对象的访问。
  2. 增强功能:可以在不修改真实对象代码的情况下,通过代理为其增加额外的功能(如延迟加载、权限控制、日志、缓存)。
  3. 降低耦合:客户端与真实对象解耦,客户端只与代理接口交互。
  4. 提高性能:通过虚拟代理延迟加载或缓存代理缓存结果,可以提高系统性能。
  5. 远程访问透明化:远程代理使得客户端可以像访问本地对象一样访问远程对象。

缺点:

  1. 增加系统复杂性:引入了额外的代理类,可能会增加系统的设计和实现的复杂度。
  2. 可能引入性能开销:由于请求需要通过代理转发,可能会增加一次间接调用,带来轻微的性能延迟。但通常这种开销被代理带来的好处(如延迟加载、缓存)所抵消或超过。
  3. 真实主题的接口依赖:代理类通常依赖于真实主题的接口,如果真实主题接口发生变化,代理类也可能需要修改。

7. 实现方式 (Implementations)

让我们以一个虚拟代理为例,实现一个图片加载器。真实图片对象加载开销大,我们希望在实际显示时才加载它。

主题接口 (Image - Subject)
// image.go (Subject interface)
package imaging

// Image 主题接口
type Image interface {
	Display()
	GetFilename() string
}
// Image.java (Subject interface)
package com.example.imaging;

// 主题接口
public interface Image {
    void display();
    String getFilename();
}
真实主题 (RealImage - RealSubject)
// real_image.go (RealSubject)
package imaging

import (
	"fmt"
	"time"
)

// RealImage 真实主题
type RealImage struct {
	filename string
}

func NewRealImage(filename string) *RealImage {
	ri := &RealImage{filename: filename}
	ri.loadFromDisk() // 创建时即加载
	return ri
}

func (ri *RealImage) GetFilename() string {
    return ri.filename
}

func (ri *RealImage) loadFromDisk() {
	fmt.Printf("RealImage: Loading image '%s' from disk...\n", ri.filename)
	// 模拟耗时操作
	time.Sleep(2 * time.Second)
	fmt.Printf("RealImage: Image '%s' loaded.\n", ri.filename)
}

func (ri *RealImage) Display() {
	fmt.Printf("RealImage: Displaying image '%s'\n", ri.filename)
}
// RealImage.java (RealSubject)
package com.example.imaging;

// 真实主题
public class RealImage implements Image {
    private String filename;

    public RealImage(String filename) {
        this.filename = filename;
        loadFromDisk(); // 创建时即加载
    }

    @Override
    public String getFilename() {
        return filename;
    }

    private void loadFromDisk() {
        System.out.printf("RealImage: Loading image '%s' from disk...%n", filename);
        try {
            // 模拟耗时操作
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.err.println("Loading interrupted for " + filename);
        }
        System.out.printf("RealImage: Image '%s' loaded.%n", filename);
    }

    @Override
    public void display() {
        System.out.printf("RealImage: Displaying image '%s'%n", filename);
    }
}
代理类 (ProxyImage - Proxy)
// proxy_image.go (Proxy)
package imaging

import "fmt"

// ProxyImage 代理类 (虚拟代理)
type ProxyImage struct {
	filename  string
	realImage *RealImage // 指向真实对象的指针,延迟初始化
}

func NewProxyImage(filename string) *ProxyImage {
	fmt.Printf("ProxyImage: Created proxy for image '%s'. Real image not loaded yet.\n", filename)
	return &ProxyImage{filename: filename, realImage: nil}
}

func (pi *ProxyImage) GetFilename() string {
    return pi.filename
}

func (pi *ProxyImage) Display() {
	if pi.realImage == nil { // 延迟加载
		fmt.Printf("ProxyImage: Real image '%s' needs to be displayed. Loading now...\n", pi.filename)
		pi.realImage = NewRealImage(pi.filename)
	}
	pi.realImage.Display() // 委托给真实对象
}
// ProxyImage.java (Proxy)
package com.example.imaging;

// 代理类 (虚拟代理)
public class ProxyImage implements Image {
    private String filename;
    private RealImage realImage; // 指向真实对象的引用,延迟初始化

    public ProxyImage(String filename) {
        this.filename = filename;
        System.out.printf("ProxyImage: Created proxy for image '%s'. Real image not loaded yet.%n", filename);
    }

    @Override
    public String getFilename() {
        return filename;
    }

    @Override
    public void display() {
        if (realImage == null) { // 延迟加载
            System.out.printf("ProxyImage: Real image '%s' needs to be displayed. Loading now...%n", filename);
            realImage = new RealImage(filename);
        }
        realImage.display(); // 委托给真实对象
    }
}
客户端使用
// main.go (示例用法)
/*
package main

import (
	"./imaging"
	"fmt"
)

func main() {
	fmt.Println("--- Client: Creating proxy images ---")
	image1 := imaging.NewProxyImage("photo1.jpg")
	image2 := imaging.NewProxyImage("document_scan.png")

	// 此时,真实图片尚未加载
	fmt.Printf("\nImage 1 Filename: %s\n", image1.GetFilename())
	fmt.Printf("Image 2 Filename: %s\n", image2.GetFilename())

	fmt.Println("\n--- Client: Requesting to display image1 ---")
	image1.Display() // 第一次调用 Display,会触发真实图片加载

	fmt.Println("\n--- Client: Requesting to display image1 again ---")
	image1.Display() // 第二次调用 Display,真实图片已加载,直接显示

	fmt.Println("\n--- Client: Requesting to display image2 ---")
	image2.Display() // 第一次调用 Display for image2,会触发加载
}
*/
// Main.java (示例用法)
/*
package com.example;

import com.example.imaging.Image;
import com.example.imaging.ProxyImage;

public class Main {
    public static void main(String[] args) {
        System.out.println("--- Client: Creating proxy images ---");
        Image image1 = new ProxyImage("photo1.jpg");
        Image image2 = new ProxyImage("document_scan.png");

        // 此时,真实图片尚未加载
        System.out.printf("%nImage 1 Filename: %s%n", image1.getFilename());
        System.out.printf("Image 2 Filename: %s%n", image2.getFilename());

        System.out.println("%n--- Client: Requesting to display image1 ---");
        image1.display(); // 第一次调用 display,会触发真实图片加载

        System.out.println("%n--- Client: Requesting to display image1 again ---");
        image1.display(); // 第二次调用 display,真实图片已加载,直接显示

        System.out.println("%n--- Client: Requesting to display image2 ---");
        image2.display(); // 第一次调用 display for image2,会触发加载
    }
}
*/

8. 与装饰器模式的区别

代理模式和装饰器模式在结构上非常相似(都包装了另一个对象并实现了相同的接口),但它们的意图截然不同:

  • 代理模式 (Proxy)

    • 意图:控制对对象的访问。代理决定客户端是否、何时以及如何访问真实对象。
    • 关注点:访问控制、生命周期管理(如虚拟代理)、通信(如远程代理)。
    • 客户端感知:客户端可能不知道它正在与代理交互(例如,远程代理或保护代理对客户端是透明的),也可能知道(例如,客户端显式创建一个虚拟代理)。
    • 谁创建:代理通常由系统或框架创建和管理,或者客户端在特定场景下创建(如虚拟代理)。
  • 装饰器模式 (Decorator)

    • 意图:动态地向对象添加额外的职责或行为,而不改变其接口。
    • 关注点:增强对象的功能。
    • 客户端感知:客户端通常知道它正在使用一个装饰过的对象,并且通常负责构建装饰链。
    • 谁创建:装饰器通常由客户端根据需要动态地组合和应用。

简单来说:

  • 代理:我是“替身”或“看门的”,我管着你怎么用那个真实的东西。
  • 装饰器:我是“加料的”,我给那个真实的东西增加新花样。

9. 总结

代理模式是一种强大的结构型模式,它通过引入一个代理对象来控制对真实对象的访问。这种间接性使得我们可以在不修改真实对象代码的情况下,实现诸如延迟加载、权限控制、远程访问、日志记录、缓存等多种功能。根据具体需求,可以选择不同类型的代理(虚拟代理、保护代理、远程代理等)来解决特定的问题。

记住它的核心:提供替身,控制访问,增强间接性

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2391780.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

架构意识与性能智慧的双重修炼

架构意识与性能智慧的双重修炼 ——现代软件架构师的核心能力建设指南 作者:蓝葛亮 🎯引言 在当今快速发展的技术环境中,软件架构师面临着前所未有的挑战。随着业务复杂度的不断增长和用户对性能要求的日益严苛,如何在架构设计中平衡功能实现与性能优化,已成为每个技术…

Dynamics 365 Business Central AI Sales Order Agent Copilot

#AI Copilot# #D365 BC 26 Wave# 最近很多客户都陆续升级到 Dynamics 365 Business Central 26 wave, Microsoft 提供一个基于Copilot 的Sales Order Agent,此文将此功能做个介绍. Explorer: 可以看到26版本上面增加了这样一个新图标。 Configuration: 配置过程…

RabbitMQ 与其他 MQ 的对比分析:Kafka/RocketMQ 选型指南(一)

一、引言 ** 在当今分布式系统大行其道的技术时代,消息队列作为分布式系统的关键组件,起着举足轻重的作用。它就像是一个可靠的信使,在不同的系统模块、服务之间传递信息,让各个部分能够高效、稳定地协同工作。消息队列能够实现系…

汽车EPS系统的核心:驱动芯片的精准控制原理

随着科技的飞速发展,电机及其驱动技术在现代工业、汽车电子、家用电器等领域扮演着越来越重要的角色。有刷马达因其结构简单、成本低廉、维护方便等优点,在市场上占据了一定的份额。然而,为了充分发挥有刷马达的性能,一款高效能、…

【Linux网络编程】传输层协议TCP,UDP

目录 一,UDP协议 1,UDP协议的格式 2,UDP的特点 3,面向数据报 4,UDP的缓冲区 5,UDP使用注意事项 6,基于UDP的应用层协议 二,对于报文的理解 三,TCP协议 1&…

基于Geotools的Worldpop世界人口tif解析-以中国2020年数据为例

目录 前言 一、Worldpop数据简介 1、数据来源 2、QGIS数据展示 3、元数据展示 二、GeoTools人口解析 1、Maven依赖引入 2、Tif人口计算 三、总结 前言 在当今数字化与信息化飞速发展的时代,地理空间数据的分析与应用已然成为诸多领域研究与决策的关键支撑。…

Unity3D仿星露谷物语开发55之保存游戏到文件

1、目标 将游戏保存到文件,并从文件中加载游戏。 Player在游戏中种植的Crop,我们希望保存到文件中,当游戏重新加载时Crop的GridProperty数据仍然存在。这次主要实现保存地面属性(GridProperties)信息。 我们要做的是…

【无标题】C++23新特性:支持打印volatile指针

文章目录 前言背景与问题C23的解决方案实现原理使用场景硬件开发多线程调试 总结 前言 在C开发中,volatile关键字常用于修饰变量,以确保编译器不会对这些变量进行优化,从而保证程序能够正确地与硬件交互或处理多线程环境下的特殊变量。然而&…

【第4章 图像与视频】4.2 图像的缩放

文章目录 前言示例-图像的缩放在 Canvas 边界之外绘制图像 前言 在上节中读者已经学会了如何使用 drawImage() 方法将一幅未经缩放的图像绘制到 canvas 之中。现在我们就来看看如何用该方法在绘制图像的时候进行缩放 示例-图像的缩放 未缩放的图像,显示图形原有大…

敏捷开发中如何避免迭代失控

在敏捷开发过程中避免迭代失控,需要实施合理规划迭代目标、明确职责分工、强化沟通机制、严格控制需求变更等措施,其中合理规划迭代目标尤为重要,它确保团队聚焦于关键任务,避免因目标不清晰而导致的迭代混乱和失控。 一、合理规划…

Python开发AI智能体(九)———构建RAG对话应用

前言 上篇文章我们介绍了如何在Langchain中构建代理 这篇文章我们将带领大家构建一个RAG对话应用 一、什么是RAG对话应用? RAG(Retrieval-Augmented Generation,检索增强生成)技术通过从外部知识库检索相关信息,并将…

NW907NW918美光固态闪存NW920NW930

NW907NW918美光固态闪存NW920NW930 技术解析:美光NW系列固态闪存的核心突破 美光NW907、NW918、NW920、NW930四款固态闪存产品,代表了当前存储技术的顶尖水平。其核心创新在于G9 NAND架构的深度优化,采用更先进的5纳米制程工艺,…

【Deepseek 学网络互联】跨节点通信global 和节点内通信CLAN保序

Clan模式下的源端保序与Global类似,目的端保序则退化成通道保序,此时仅支持网络单路径保序。”这里的通道保序怎么理解? 用户可能正在阅读某种硬件架构文档(比如NVIDIA的NVLink或InfiniBand规范),因为"…

9.5 Q1 | 北京协和医学院GBD发文 | 1990-2021 年全球、区域和国家心力衰竭负担及其根本原因

1.第一段-文章基本信息 文章题目:Global, regional, and national burden of heart failure and its underlying causes, 1990-2021: results from the global burden of disease study 2021 中文标题:1990-2021 年全球、区域和国家心力衰竭负担及其根本…

根据Cortex-M3(包括STM32F1)权威指南讲解MCU内存架构与如何查看编译器生成的地址具体位置

首先我们先查看官方对于Cortex-M3预定义的存储器映射 1.存储器映射 1.1 Cortex-M3架构的存储器结构 内部私有外设总线:即AHB总线,包括NVIC中断,ITM硬件调试,FPB, DWT。 外部私有外设总线:即APB总线,用于…

MCP入门实战(极简案例)

MCP简介 MCP(Model Context Protocol,模型上下文协议)2024年11月底由 Antbropic 推出的一种开放标准,旨在统一大型语言模型(LLM)与外部数据源和工具之间的通信协议。 Function Calling是AI模型调用函数的机制,MCP是一个标准协议,使AI模型与API无缝交互,而Al Agent是一个…

Cursor从入门到精通实战指南(一):开始使用Cursor

一、简介与核心优势 Cursor是一款基于VSCode开发的AI编程工具,集成了GPT-4、Claude 3.5等先进大语言模型,支持代码补全、生成、重构、调试等功能。其核心优势包括: 高效协作:通过自然语言对话实现代码开发,支持跨文件…

计算机组成原理——cache

3.4cache 出自up主Beokayy传送门 1.局部性原理 时间局部性: 在最近的未来要用到的信息,很可能是现在正在使用的信息,因为程序中存在循环。 空间局部性: 在最近的未来要用到的信息,很可能与现在正在使用的信息在存储…

EasyExcel使用导出模版后设置 CellStyle失效问题解决

EasyExcel使用导出模版后在CellWriteHandler的afterCellDispose方法设置 CellStyle失效问题解决方法 问题描述:excel 模版塞入数据后,需要设置单元格的个性化设置时失效,本文以设置数据格式为例(设置列的数据展示时需要加上千分位…

Knife4j框架的使用

文章目录 引入依赖配置Knife4j使用Knife4j 访问 SpringBoot 生成的文档 Knife4j 是基于 Swagger 的增强工具&#xff0c;对 Swagger 进行了拓展和优化&#xff0c;从而有更美观的界面设计和更强的功能 引入依赖 Spring Boot 2.7.18 版本 <dependency> <groupId>c…