
系列定位软考软件设计师 / 系统架构设计师 — 结构型模式专题第 6 讲结构型模式收官篇考察分值上午题 1-2 分下午题常作为代码填空或类图识别出现难度等级⭐⭐⭐☆☆结构直观但与装饰模式的辨析是高频失分点一、考纲定位与模式定义1.1 考纲要求代理模式在软考中属于结构型模式的基础内容。考察形式包括上午选择题判断场景描述所属模式识别代理模式与装饰模式、外观模式的区别判断代理的类型远程代理、虚拟代理、保护代理、智能引用下午设计题补全 Proxy 类的request()方法在调用 RealSubject 前后插入控制逻辑补全 Proxy 构造器中注入 RealSubject 的代码识别类图中 Subject、RealSubject、Proxy 三个角色1.2 模式定义代理模式为其他对象提供一种代理以控制对这个对象的访问。核心意图当客户端需要访问某个对象RealSubject但直接访问存在困难或不适宜时如对象在远程、创建成本高、需要权限控制引入一个代理对象Proxy作为中间层。Proxy 与 RealSubject 实现相同的接口客户端对 Proxy 的调用在内部被转发给 RealSubject同时 Proxy 可以在转发前后添加额外的控制逻辑如延迟加载、权限检查、日志记录、远程通信等。通俗理解你想见一位明星RealSubject但明星很忙不能直接见所有人。于是你联系明星的经纪人Proxy。经纪人和明星对外提供同样的 见面 服务但经纪人在安排见面之前会先检查你的身份权限控制、协调时间延迟加载/资源管理、甚至远程视频通话远程代理。你自始至终以为自己在和明星打交道实际上大部分时候是经纪人在中间协调。关键特征代理模式对客户端是透明的 —— 客户端以为自己直接调用了目标对象实际上调用的是代理对象。代理对象和目标对象实现相同的接口。二、UML 类图与角色划分┌─────────────────────────────────────────────────────────┐ │ Client │ │ main() │ │ ├── Subject sub new Proxy(); // 客户端以为在调用 │ │ └── sub.request(); // 真实对象其实是代理 │ └───────────────────────┬─────────────────────────────────┘ │ uses ▼ ┌─────────────────────────────────────────────────────────┐ │ interface │ │ Subject │ │ request() │ └───────────────────────┬─────────────────────────────────┘ ┌────────────┴────────────┐ △ implements △ implements │ │ ▼ ▼ ┌─────────────────────┐ ┌─────────────────────┐ │ RealSubject │ │ Proxy │ │ request() { │ │ - realSubject: │ │ // 真实业务逻辑 │ │ RealSubject │ │ } │ │ request() { │ │ │ │ // 前置控制逻辑 │ │ │ │ realSubject. │ │ │ │ request(); │ │ │ │ // 后置控制逻辑 │ │ │ │ } │ └─────────────────────┘ └─────────────────────┘角色职责软考填空关键词Subject抽象主题定义 RealSubject 和 Proxy 的共同接口让 Proxy 可以替代 RealSubjectinterface/abstract classrequest()RealSubject真实主题定义 Proxy 所代表的真实对象完成核心业务逻辑真实业务类如ImageLoader、DocumentServiceProxy代理保存一个 RealSubject 的引用控制对 RealSubject 的访问在访问前后执行附加操作implements Subjectprivate RealSubject realSubject 控制逻辑Client客户端面向 Subject 接口编程通过 Proxy 间接访问 RealSubjectSubject sub new Proxy()类图识别要点软考类图中代理模式的核心特征是Proxy 与 RealSubject 实现同一个 Subject 接口且 Proxy 内部持有 RealSubject 的引用组合关系。如果类图里 Decorator 与 Component 是递归组合 → 装饰如果 Proxy 只控制一个 RealSubject 的访问 → 代理。三、场景一保护代理 —— 权限控制最常用业务背景系统有一个文档服务DocumentServiceRealSubject提供read()和write()方法。现在需要增加权限控制只有管理员才能写入普通用户只能读取。不能修改DocumentService的源码。使用保护代理实现。说明保护代理是代理模式最常见的类型之一通过代理对象在调用真实对象前检查调用者的权限。这是软考下午题中 权限控制 场景的标准解法。3.1 代码实现// Subject文档服务接口 public interface DocumentService { String read(String docId); void write(String docId, String content); } // RealSubject真实文档服务 public class RealDocumentService implements DocumentService { Override public String read(String docId) { System.out.println([真实服务] 读取文档: docId); return 文档 docId 的内容; } Override public void write(String docId, String content) { System.out.println([真实服务] 写入文档: docId , 内容: content); } } // Proxy保护代理 public class DocumentServiceProxy implements DocumentService { private RealDocumentService realService; private String currentUser; private String userRole; public DocumentServiceProxy(String user, String role) { this.currentUser user; this.userRole role; this.realService new RealDocumentService(); } Override public String read(String docId) { System.out.println([代理] 用户 currentUser 请求读取文档: docId); String result realService.read(docId); System.out.println([代理] 读取完成); return result; } Override public void write(String docId, String content) { System.out.println([代理] 用户 currentUser 请求写入文档: docId); if (!admin.equals(userRole)) { System.out.println([代理] 拒绝访问权限不足需要 admin 角色); return; } realService.write(docId, content); System.out.println([代理] 写入完成操作人已记录); } } // Client客户端 public class Client { public static void main(String[] args) { DocumentService adminService new DocumentServiceProxy(张三, admin); adminService.read(DOC001); adminService.write(DOC001, 新内容); System.out.println(---); DocumentService userService new DocumentServiceProxy(李四, user); userService.read(DOC001); userService.write(DOC001, 内容); } }关键体会客户端面向DocumentService接口编程不知道自己在调用 Proxy。Proxy 在write()方法中增加了权限检查前置控制未通过时直接拒绝不调用 RealSubject。Proxy 还增加了日志记录前置后置控制这些横切关注点不污染 RealSubject 的业务代码。四、场景二虚拟代理 —— 延迟加载大对象创建业务背景系统需要显示高清图片图片加载非常耗时从磁盘或网络读取。如果在创建对象时就立即加载图片会阻塞界面。使用虚拟代理在真正需要显示图片时才加载创建对象时只显示一个占位符。说明虚拟代理是代理模式的经典类型常用于图片加载、文档预览等场景。这是软考上午题中 延迟加载 场景的标准答案。4.1 代码实现// Subject图片显示接口 public interface Image { void display(); String getFilename(); } // RealSubject真实图片加载成本高 public class RealImage implements Image { private String filename; public RealImage(String filename) { this.filename filename; loadFromDisk(); } private void loadFromDisk() { System.out.println([真实图片] 从磁盘加载图片: filename 耗时操作); } Override public void display() { System.out.println([真实图片] 显示图片: filename); } Override public String getFilename() { return filename; } } // Proxy虚拟代理延迟加载 public class ProxyImage implements Image { private String filename; private RealImage realImage; public ProxyImage(String filename) { this.filename filename; System.out.println([代理图片] 创建代理对象: filename 未加载图片); } Override public void display() { if (realImage null) { System.out.println([代理图片] 首次显示开始加载真实图片...); realImage new RealImage(filename); } realImage.display(); } Override public String getFilename() { return filename; } } // Client public class Client { public static void main(String[] args) { Image[] images { new ProxyImage(photo1.jpg), new ProxyImage(photo2.jpg), new ProxyImage(photo3.jpg) }; System.out.println(图片列表创建完成内存占用很小\n); System.out.println(--- 用户点击 photo1.jpg ---); images[0].display(); System.out.println(\n--- 再次点击 photo1.jpg ---); images[0].display(); System.out.println(\n--- 用户点击 photo3.jpg ---); images[2].display(); } }关键体会ProxyImage在构造时不创建RealImage只保存文件名。创建 1000 个 ProxyImage 对象很快内存占用很小。第一次调用display()时才new RealImage(filename)触发耗时加载。这就是延迟加载Lazy Loading。第二次调用display()时realImage已存在直接委托无需重复加载。软考高频考点虚拟代理 延迟加载。看到 创建对象成本高、首次使用时才加载、占位符 → 虚拟代理。五、场景三远程代理 —— 模拟远程方法调用RMI 风格业务背景客户端需要调用远程服务器上的订单服务。直接调用远程对象存在网络通信问题。使用远程代理在客户端本地创建一个代理对象代理对象负责网络通信将调用请求序列化后发送到远程服务器再将结果返回给客户端。客户端对网络通信完全无感知。说明远程代理是代理模式在分布式系统中的典型应用也是软考上午题中 远程调用 场景的标准答案。5.1 代码实现// Subject订单服务接口客户端和服务器共享 public interface OrderService { String getOrderStatus(String orderId); boolean cancelOrder(String orderId); } // RealSubject远程服务器上的真实订单服务 public class RemoteOrderService implements OrderService { Override public String getOrderStatus(String orderId) { System.out.println([远程服务器] 查询订单 orderId 状态); return 已发货; } Override public boolean cancelOrder(String orderId) { System.out.println([远程服务器] 取消订单 orderId); return true; } } // Proxy远程代理运行在客户端 public class OrderServiceProxy implements OrderService { private String serverAddress; public OrderServiceProxy(String serverAddress) { this.serverAddress serverAddress; } private Object sendRequest(String methodName, Object[] args) { System.out.println([远程代理] 连接服务器: serverAddress); System.out.println([远程代理] 发送请求: methodName); System.out.println([远程代理] 等待服务器响应...); RemoteOrderService remote new RemoteOrderService(); if (getOrderStatus.equals(methodName)) { return remote.getOrderStatus((String) args[0]); } else if (cancelOrder.equals(methodName)) { return remote.cancelOrder((String) args[0]); } return null; } Override public String getOrderStatus(String orderId) { return (String) sendRequest(getOrderStatus, new Object[]{orderId}); } Override public boolean cancelOrder(String orderId) { return (Boolean) sendRequest(cancelOrder, new Object[]{orderId}); } } // Client客户端完全无感知远程调用 public class Client { public static void main(String[] args) { OrderService orderService new OrderServiceProxy(http://server.example.com:8080); String status orderService.getOrderStatus(ORD1001); System.out.println(订单状态: status); boolean ok orderService.cancelOrder(ORD1001); System.out.println(取消结果: ok); } }六、三种场景对比与演进思路维度场景一保护代理场景二虚拟代理场景三远程代理代理类型保护代理Protection Proxy虚拟代理Virtual Proxy远程代理Remote Proxy控制目的权限检查延迟加载网络通信封装代理创建时机与真实对象同时创建先创建代理延迟创建真实对象只创建代理真实对象在远程客户端感知知道有代理显式传入用户信息完全透明以为在操作真实图片完全透明以为在调用本地服务前置逻辑权限验证判断真实对象是否已创建建立网络连接、序列化请求后置逻辑日志记录无反序列化响应、关闭连接典型应用权限控制、访问审计图片加载、大对象延迟创建RMI、Web Service、RPC软考考法下午大题补全权限检查上午选择题识别延迟加载上午选择题识别远程调用代理模式的其他类型智能引用代理Smart Reference在访问对象时执行额外操作如引用计数、线程安全检查。缓存代理Cache Proxy为开销大的操作结果提供缓存如Spring Cache的Cacheable注解底层就是缓存代理。同步代理Synchronization Proxy在多线程环境下为对象提供安全访问如 Java 的Collections.synchronizedList()。七、代理模式 vs 装饰模式 vs 外观模式核心对比对比项代理模式装饰模式外观模式设计目的控制对对象的访问增强对象的功能简化子系统的使用接口变化❌ 不改变与目标对象相同接口❌ 不改变与 Component 相同接口✅ 提供新的高层接口目标对象数量通常一个RealSubject通常一个Component可递归包装通常多个Subsystem客户端感知通常透明以为在调用目标对象通常不透明知道在层层包装不透明只知道 Facade是否新增功能❌ 不新增业务功能只加控制逻辑✅ 新增业务功能如加料、加时间戳❌ 不新增功能只是封装调用核心逻辑位置访问控制前置/后置拦截功能增强前后叠加调用顺序封装软考判断看到 权限、延迟加载、远程调用、控制访问 → 代理看到 添加职责、增强、套娃 → 装饰看到 简化子系统、统一入口 → 外观记忆口诀代理是 中间人控访问装饰是 套娃加功能外观是 简化复杂门。八、软考高频考点与易混淆辨析8.1 高频考点考点内容模式分类结构型模式GoF 23 正式成员核心三角色Subject抽象主题、RealSubject真实主题、Proxy代理核心思想通过代理对象控制对真实对象的访问在访问前后插入控制逻辑透明性代理对客户端通常是透明的Proxy 与 RealSubject 实现相同接口代理类型保护代理权限、虚拟代理延迟加载、远程代理网络通信、智能引用引用计数与装饰区别代理控制访问不新增业务功能装饰增强功能可新增业务方法与外观区别代理控制一个对象的访问外观简化多个子系统的使用适用场景① 远程代理隐藏对象位于不同地址空间的事实② 虚拟代理延迟加载③ 保护代理访问控制④ 智能引用引用计数、日志Java 典型例子RMI远程代理、Spring AOP动态代理、Hibernate延迟加载代理8.2 易混淆辨析代理 vs 装饰最难辨析对比项代理模式装饰模式UML 结构几乎相同都实现同一接口都组合目标对象几乎相同都实现同一接口都组合目标对象设计意图控制访问何时调用、是否允许调用增强功能调用前后添加新功能是否新增接口方法通常不新增保持与 Subject 一致可以新增方法如readLine()目标对象生命周期代理通常管理目标对象的生命周期创建、缓存装饰者通常不管理只由客户端传入客户端是否知道通常不知道透明代理通常知道显式层层包装典型例子权限控制、延迟加载、远程调用咖啡加料、Java IO 流增强软考判断看到 控制、权限、延迟、远程 → 代理看到 增强、添加、叠加 → 装饰终极判断技巧如果代理类新增了Subject接口中没有的方法 → 装饰如果代理类完全保持原接口只是控制调用时机或条件 → 代理。如果题干明确说 控制访问 → 代理添加功能 → 装饰。九、真题风格模拟与代码填空模拟题 1上午选择题以下关于代理模式的叙述中正确的是。A. 代理模式属于创建型模式主要用于创建对象的代理副本B. 在代理模式中Proxy 通过继承 RealSubject 来获得真实对象的功能C. 代理模式可以在不改变目标对象接口的前提下控制对目标对象的访问D. 代理模式与装饰模式的核心区别在于代理模式增强对象功能装饰模式控制对象访问答案C解析A 错误代理属于结构型模式不是创建型。B 错误Proxy 通过组合持有 RealSubject 引用来访问真实对象不是继承。如果用继承就无法在运行时灵活替换目标对象。C 正确这是代理模式的核心定义 —— 控制访问且保持接口不变透明代理。D 错误说反了。代理模式控制访问不改变功能装饰模式增强功能不改变接口。模拟题 2下午代码填空 — 补全保护代理某系统使用代理模式实现文件访问的权限控制。FileService接口提供read()和delete()方法。RealFileService是真实实现。FileServiceProxy是保护代理只有admin角色才能执行delete()。请补全1~4。// Subject interface FileService { String read(String filename); void delete(String filename); } // RealSubject class RealFileService implements FileService { public String read(String filename) { System.out.println(读取文件: filename); return 内容; } public void delete(String filename) { System.out.println(删除文件: filename); } } // Proxy class FileServiceProxy implements FileService { private (1)______ realService; private String role; public FileServiceProxy(String role) { this.role role; this.realService (2)______; } public String read(String filename) { return realService.read(filename); } public void delete(String filename) { if ((3)______) { System.out.println([代理] 拒绝删除权限不足); return; } (4)______; } } public class Client { public static void main(String[] args) { FileService service new FileServiceProxy(admin); service.delete(test.txt); } }答案(1)RealFileService或FileService(2)new RealFileService()(3)!admin.equals(role)(4)realService.delete(filename)阅卷要点(1) 写RealFileService或FileService都算对。写FileService更符合面向接口编程但RealFileService在实际代码中更常见。(2) 必须是new RealFileService()创建真实对象。如果写new FileService()会零分接口不能实例化。(3) 必须是权限检查逻辑判断角色不是 admin 时拒绝。如果条件写反了admin.equals(role)会零分因为那样是 admin 才能删除 的反面。(4) 必须是realService.delete(filename)将删除操作委托给真实对象。如果写this.delete(filename)会死循环零分。模拟题 3下午代码填空 — 虚拟代理延迟加载某系统使用虚拟代理实现大对象的延迟加载。DataLoader接口提供load()方法。RealDataLoader在构造时加载大量数据耗时。DataLoaderProxy在首次调用load()时才创建RealDataLoader。请补全1~3。// Subject interface DataLoader { String load(); } // RealSubject class RealDataLoader implements DataLoader { private String source; public RealDataLoader(String source) { this.source source; System.out.println([真实对象] 从 source 加载大量数据耗时); } public String load() { return 数据来自 source; } } // Proxy class DataLoaderProxy implements DataLoader { private String source; private (1)______ realLoader; public DataLoaderProxy(String source) { this.source source; } public String load() { if ((2)______) { System.out.println([代理] 首次加载创建真实对象...); realLoader (3)______; } return realLoader.load(); } } public class Client { public static void main(String[] args) { DataLoader loader new DataLoaderProxy(database); System.out.println(代理对象已创建未加载数据); String data loader.load(); System.out.println(data); } }答案(1)RealDataLoader或DataLoader(2)realLoader null(3)new RealDataLoader(source)阅卷要点(1) 必须是RealDataLoader或DataLoader。这是代理持有的真实对象引用。(2) 必须是realLoader null判断真实对象是否已创建。这是虚拟代理的核心逻辑 —— 延迟加载的判断条件。(3) 必须是new RealDataLoader(source)创建真实对象并传入数据源。如果写new DataLoader(source)会零分接口不能实例化。十、常见陷阱与注意事项陷阱 1误认为代理是创建型模式代理属于结构型模式因为它解决的是对象之间的结构关系通过中间层控制访问而不是对象的创建问题。软考上午题如果问以下属于创建型模式的是选项里出现代理不能选。陷阱 2Proxy 继承 RealSubject 而不是组合这是软考常见错误写法。代理模式的核心是组合❌ 错误class Proxy extends RealSubject继承静态绑定失去灵活性✅ 正确class Proxy implements Subject { private RealSubject real; }组合运行时注入软考代码填空里Proxy 必须持有private RealSubject xxx字段或private Subject xxx字段通过组合关联调用真实对象。陷阱 3与装饰模式混淆这是软考最高频、最难的辨析题。两者的 UML 结构几乎完全相同都实现同一个接口都组合目标对象都在方法前后插入逻辑终极区分方法看意图和新增方法。代理为了控制访问权限、延迟、远程。Proxy不新增Subject 接口之外的方法。装饰为了增强功能加料、加时间戳。Decorator可以新增Component 接口之外的方法如readLine()。如果代码填空里 Proxy 只做了前置/后置控制没有新增方法 → 代理。如果新增了方法 → 装饰。陷阱 4与外观模式混淆代理模式外观模式控制一个对象的访问简化多个子系统的使用Proxy 与 RealSubject 实现同一接口Facade 提供新的高层接口目的是控制目的是简化如果类图里只有一个目标对象Proxy 与它实现同一接口 → 代理。如果类图里 Facade 连接多个子系统Client 只连 Facade → 外观。陷阱 5远程代理的透明性远程代理的核心价值是透明性—— 客户端完全不知道自己在进行远程调用。如果客户端代码里出现了网络相关的代码如socket.connect()、httpRequest.send()那就不是代理模式而是客户端直接处理了远程通信。代理模式要求这些网络细节完全封装在 Proxy 内部。陷阱 6虚拟代理的重复创建虚拟代理中realSubject null的判断必须正确否则会导致重复创建真实对象失去延迟加载的意义。软考代码填空里这个判断是核心判分点。陷阱 7保护代理的权限检查位置权限检查必须在调用 RealSubject 之前前置控制。如果在调用之后检查那就失去了保护的意义。软考代码填空里如果权限检查写在realService.delete()之后会扣分。十一、总结要点内容定义为其他对象提供一种代理以控制对这个对象的访问分类结构型模式GoF 23 正式成员核心三角色Subject抽象主题、RealSubject真实主题、Proxy代理核心思想通过代理对象作为中间层控制对真实对象的访问在访问前后插入控制逻辑透明性代理对客户端通常是透明的Proxy 与 RealSubject 实现相同接口代理类型保护代理权限、虚拟代理延迟加载、远程代理网络通信、智能引用引用计数与装饰区别代理控制访问不新增业务功能装饰增强功能可新增业务方法与外观区别代理控制一个对象的访问外观简化多个子系统的使用适用场景远程访问、延迟加载、权限控制、访问审计、引用计数软考重点代码填空补全 Proxy 控制逻辑 委托调用与装饰模式的辨析最高频代理类型识别答题技巧看到 权限控制、延迟加载、远程调用、控制访问 → 代理看到 Proxy 与 RealSubject 同接口且客户端透明 → 确认代理结构型模式总结结构型模式六兄弟适配器、桥接、装饰、外观、享元、代理已全部讲完。它们共同解决的是类与类之间的组合关系问题适配器转接口、桥接分维度、装饰套娃增强、外观简化入口、享元共享对象、代理控制访问。下一篇将开启行为型模式篇章从策略模式开始 —— 当算法需要灵活切换时如何将算法族封装为独立的策略类让客户端按需选择。咱们下回见。