代理模式完全解析:从直接访问到智能控制的访问代理
提供一个代理模式的详细解析覆盖代理模式的定义、生活比喻、核心机制、UML建模、项目结构、代码实现等。实例选用一个合适的场景例如图片加载代理延迟加载或访问控制代理。嵌入式或应用软件均可。提供一个完整的Markdown文档包含UMLmermaid代码实现C语言文件结构编译运行示例等。场景一个文档查看器其中包含大量图片使用代理模式实现图片的延迟加载只有当图片真正需要显示时才加载。这样既体现了代理控制访问延迟加载的好处。将按照以下结构组织引言问题描述不推荐做法一次性加载所有图片导致内存浪费/启动慢代理模式定义、生活比喻、核心机制UML类图mermaid项目文件结构详细代码实现抽象主题接口Image真实主题RealImage加载和显示图片代理ImageProxy控制对RealImage的访问延迟加载客户端代码main编译运行说明深入解析设计要点总结注意代码风格使用C语言函数指针模拟接口。代理模式完全解析从直接访问到智能控制的访问代理1. 核心问题如何控制对对象的访问延迟加载、权限控制、日志记录在软件开发中有时我们并不希望直接访问某个对象因为直接访问可能会带来性能问题如创建大对象开销高、安全风险如未授权访问或需要额外的管理逻辑如记录日志。例如一个文档查看器包含大量高分辨率图片如果程序启动时将所有图片加载到内存会导致启动缓慢和内存浪费。不推荐的做法在程序初始化时一次性创建所有资源。// 不推荐启动时加载所有图片voidinit_viewer(){for(inti0;i100;i){Image*imgcreate_real_image(image_paths[i]);// 立即加载到内存add_to_viewer(img);}}代理模式Proxy Pattern为其他对象提供一种代理以控制对这个对象的访问。代理对象与真实对象实现相同的接口客户端通过代理间接访问真实对象。代理可以在调用真实对象前后执行额外的操作如延迟加载、访问控制、日志记录等。2. 代理模式 UML 建模以图片延迟加载为例2.1 经典代理模式类图creates on demand«interface»Imagedisplay()RealImage-filename: stringdisplay()-loadFromDisk()ImageProxy-realImage: RealImage-filename: stringdisplay()Client2.2 嵌入式C语言实现结构函数指针模拟接口«struct»Imagedisplay: funcdestroy: funcRealImagebase: Imagefilename: char[]loadFromDisk()ImageProxybase: ImagerealImage: Imagefilename: char[]display()destroy()3. 实例文档查看器中的图片延迟加载代理3.1 需求描述设计一个文档查看器文档中包含多张图片。为了优化内存和启动速度只有当图片真正需要显示例如用户滚动到该位置时才加载图片数据。使用代理模式实现图片的延迟加载ImageProxy是RealImage的代理在第一次调用display()时才创建并加载RealImage。3.2 项目文件结构proxy_pattern/ ├── main.c # 客户端文档查看器 ├── image.h # 抽象主题接口 ├── real_image.c # 真实主题加载和显示图片 ├── real_image.h ├── image_proxy.c # 代理延迟加载 ├── image_proxy.h ├── Makefile └── README.md3.3 抽象主题接口image.h// image.h#ifndefIMAGE_H#defineIMAGE_H// 抽象图像接口typedefstructImage{void(*display)(structImage*self);void(*destroy)(structImage*self);}Image;#endif3.4 真实主题real_image.h / .cRealImage模拟从磁盘加载图片开销较大。// real_image.h#ifndefREAL_IMAGE_H#defineREAL_IMAGE_H#includeimage.hImage*RealImage_Create(constchar*filename);#endif// real_image.c#includereal_image.h#includestdio.h#includestdlib.h#includestring.htypedefstruct{Image base;char*filename;// 模拟图片数据实际可能是像素数组void*image_data;}RealImage;// 模拟从磁盘加载图片耗时操作staticvoidload_from_disk(RealImage*self){printf([RealImage] Loading image from %s... (heavy operation)\n,self-filename);// 模拟分配内存、读取文件等self-image_datamalloc(1024*1024);// 假设1MB图片if(self-image_data){printf([RealImage] Loaded %s successfully.\n,self-filename);}}staticvoidreal_display(Image*self){RealImage*img(RealImage*)self;if(!img-image_data){// 如果还没有加载则先加载但真实主题不应负责延迟逻辑这里仅为安全load_from_disk(img);}printf([RealImage] Displaying %s on screen.\n,img-filename);}staticvoidreal_destroy(Image*self){RealImage*img(RealImage*)self;if(img-image_data){free(img-image_data);}free(img-filename);free(img);printf([RealImage] Destroyed.\n);}Image*RealImage_Create(constchar*filename){RealImage*img(RealImage*)malloc(sizeof(RealImage));if(!img)returnNULL;img-base.displayreal_display;img-base.destroyreal_destroy;img-filenamestrdup(filename);img-image_dataNULL;// 注意不在构造函数中加载延迟到 displayreturn(Image*)img;}3.5 代理image_proxy.h / .cImageProxy持有真实图片的引用首次调用display时才创建真实对象。// image_proxy.h#ifndefIMAGE_PROXY_H#defineIMAGE_PROXY_H#includeimage.hImage*ImageProxy_Create(constchar*filename);#endif// image_proxy.c#includeimage_proxy.h#includereal_image.h#includestdio.h#includestdlib.h#includestring.htypedefstruct{Image base;char*filename;Image*real_image;// 真实图片延迟初始化}ImageProxy;staticvoidensure_real_image(ImageProxy*proxy){if(!proxy-real_image){printf([ImageProxy] Creating real image for %s (first time).\n,proxy-filename);proxy-real_imageRealImage_Create(proxy-filename);}}staticvoidproxy_display(Image*self){ImageProxy*proxy(ImageProxy*)self;ensure_real_image(proxy);proxy-real_image-display(proxy-real_image);}staticvoidproxy_destroy(Image*self){ImageProxy*proxy(ImageProxy*)self;if(proxy-real_image){proxy-real_image-destroy(proxy-real_image);}free(proxy-filename);free(proxy);printf([ImageProxy] Destroyed.\n);}Image*ImageProxy_Create(constchar*filename){ImageProxy*proxy(ImageProxy*)malloc(sizeof(ImageProxy));if(!proxy)returnNULL;proxy-base.displayproxy_display;proxy-base.destroyproxy_destroy;proxy-filenamestrdup(filename);proxy-real_imageNULL;return(Image*)proxy;}3.6 客户端代码main.c客户端只依赖Image接口不知道使用的是代理还是真实对象。// main.c#includestdio.h#includeimage.h#includeimage_proxy.h// 模拟文档中的图片列表#defineNUM_IMAGES3intmain(void){printf( Proxy Pattern Demo: Image Lazy Loading \n);// 创建图片代理列表不立即加载真实图片Image*images[NUM_IMAGES];images[0]ImageProxy_Create(photo1.jpg);images[1]ImageProxy_Create(photo2.jpg);images[2]ImageProxy_Create(photo3.jpg);printf(\n--- Document loaded, images not yet loaded. ---\n);// 模拟用户滚动只显示部分图片printf(\n--- User scrolls to view first image ---\n);images[0]-display(images[0]);printf(\n--- User scrolls to view second image ---\n);images[1]-display(images[1]);printf(\n--- User goes back to first image (already loaded) ---\n);images[0]-display(images[0]);// 第二次显示无需重新加载// 清理for(inti0;iNUM_IMAGES;i){images[i]-destroy(images[i]);}return0;}3.7 编译与运行MakefileCC gcc CFLAGS -Wall -g OBJS main.o real_image.o image_proxy.o all: proxy_demo proxy_demo: $(OBJS) $(CC) -o $ $^ %.o: %.c $(CC) $(CFLAGS) -c $ clean: rm -f *.o proxy_demo运行输出示例 Proxy Pattern Demo: Image Lazy Loading --- Document loaded, images not yet loaded. --- --- User scrolls to view first image --- [ImageProxy] Creating real image for photo1.jpg (first time). [RealImage] Loading image from photo1.jpg... (heavy operation) [RealImage] Loaded photo1.jpg successfully. [RealImage] Displaying photo1.jpg on screen. --- User scrolls to view second image --- [ImageProxy] Creating real image for photo2.jpg (first time). [RealImage] Loading image from photo2.jpg... (heavy operation) [RealImage] Loaded photo2.jpg successfully. [RealImage] Displaying photo2.jpg on screen. --- User goes back to first image (already loaded) --- [RealImage] Displaying photo1.jpg on screen. [RealImage] Destroyed. [ImageProxy] Destroyed. [RealImage] Destroyed. [ImageProxy] Destroyed. [RealImage] Destroyed. [ImageProxy] Destroyed.4. 深入解析设计要点4.1 代理模式的角色抽象主题SubjectImage接口定义了真实主题和代理的公共行为。真实主题RealSubjectRealImage执行实际的业务逻辑加载和显示图片。代理ProxyImageProxy持有对真实主题的引用控制对其访问实现延迟加载。4.2 代理模式与装饰器模式的区别模式目的关系代理模式控制对对象的访问延迟、权限等代理持有真实对象通常在编译时确定装饰器模式动态添加功能装饰器持有被装饰对象可以无限嵌套4.3 代理模式的常见变体远程代理Remote Proxy隐藏对象位于不同地址空间的事实如RPC。虚拟代理Virtual Proxy延迟创建开销大的对象本例。保护代理Protection Proxy控制访问权限。智能引用Smart Reference在访问时添加额外操作如引用计数、日志。4.4 嵌入式环境中的注意事项代理模式增加了一层间接调用但开销极小。延迟加载可以显著减少启动时间和内存占用尤其适合资源受限的嵌入式设备。代理对象本身占用少量内存几个指针可接受。5. 总结代理模式通过引入一个代理对象来控制对真实对象的访问可以在不修改真实对象的前提下添加额外的管理逻辑。其核心价值控制访问可以实现延迟加载、权限校验、日志记录等。解耦客户端与真实对象解耦便于替换和测试。性能优化避免不必要的资源消耗。在C语言中使用函数指针结构体即可轻松实现代理模式非常适合嵌入式系统中的资源管理、远程调用等场景。一句话记住“直接访问开销大找个代理来替它用到再建真对象延迟加载顶呱呱。”—— 代理模式