结构型模式详解
结构型模式关注类和对象的组合,通过组合机制来创建更复杂的结构。本章将深入探讨七种结构型模式的原理、实现方式和实际应用。
结构型模式 = 灵活组合 + 职责分离 + 接口转换
- 🧩 灵活组合:将对象组合成更复杂的结构,而无需改变其接口
- 🔄 接口适配:使不兼容的接口能够协同工作
- 📦 封装变化:将系统中易变部分与稳定部分隔离
- 🚪 简化接口:为复杂子系统提供简单统一的访问接口
结构型模式概览
| 模式 | 核心意图 | 关键特点 | 典型应用 |
|---|---|---|---|
| 适配器模式 | 接口转换 | 使不兼容接口协同工作 | 第三方库集成、旧系统兼容 |
| 桥接模式 | 分离抽象与实现 | 抽象与实现独立变化 | UI渲染、驱动程序 |
| 组合模式 | 部分-整体结构 | 统一处理单个和组合对象 | 文件系统、菜单系统 |
| 装饰器模式 | 动态扩展功能 | 运行时添加职责 | I/O流、UI组件扩展 |
| 外观模式 | 简化接口 | 统一访问复杂子系统 | API网关、库封装 |
| 享元模式 | 共享细粒度对象 | 减少内存使用 | 字符渲染、缓存池 |
| 代理模式 | 控制对象访问 | 为对象提供替代品 | 远程代理、权限控制 |
1. 适配器模式(Adapter)
1.1 模式定义
适配器模式将一个类的接口转换成客户期望的另一个接口,使原本不兼容的类可以协同工作。它充当了两个不同接口之间的桥梁,就像现实世界中的电源适配器一样。
- 适用场景
- 优点
- 缺点
- 接口不兼容:需要集成不兼容的接口时
- 复用现有类:希望复用现有类,但其接口与系统不匹配
- 中间层适配:在不同系统之间构建中间层
- 第三方库集成:集成第三方库或旧系统时
- 兼容性:使原本不兼容的类能够一起工作
- 复用性:可以复用现有的类,无需修改其代码
- 灵活性:允许在不修改原有代码的情况下扩展功能
- 开闭原则:不修改原有代码即可集成新功能
- 复杂性增加:引入新的类和间接层
- 效率问题:可能会引入一定的性能开销
- 过度使用:过度使用会导致系统难以理解和维护
- 调试困难:添加的中间层可能增加调试难度
1.2 实现方式
类适配器模式
类适配器模式通过继承来实现,适配器同时继承被适配者并实现目标接口。
1// 目标接口2public interface Target {3 void request();4}56// 被适配的类7public class Adaptee {8 public void specificRequest() {9 System.out.println("被适配类的特殊请求");10 }11}1213// 类适配器14public class ClassAdapter extends Adaptee implements Target {15 @Override16 public void request() {17 // 调用被适配类的方法18 specificRequest();19 }20}类适配器使用了Java的继承机制,所以只能继承一个类,但可以实现多个接口。这种方式的优点是可以覆盖被适配类的方法,缺点是它与Java的单继承机制冲突,限制了适配器的灵活性。
对象适配器模式
对象适配器模式通过组合来实现,适配器包含一个被适配者的实例。
1// 对象适配器2public class ObjectAdapter implements Target {3 private Adaptee adaptee;4 5 public ObjectAdapter(Adaptee adaptee) {6 this.adaptee = adaptee;7 }8 9 @Override10 public void request() {11 adaptee.specificRequest();12 }13}对象适配器使用了对象组合的方式,将一个对象包装在适配器中。这种方式更加灵活,符合"组合优于继承"的原则,被广泛应用于实际开发中。
1.3 适配器模式对比
| 特性 | 类适配器 | 对象适配器 |
|---|---|---|
| 实现方式 | 继承 | 组合 |
| 灵活性 | 较低 | 较高 |
| 适用场景 | 适配少量接口 | 适用多个被适配者 |
| 是否能覆盖被适配者方法 | 可以 | 不可以 |
| 是否能对被适配者子类适配 | 不能 | 可以 |
| 代码复杂度 | 较简单 | 适中 |
| 符合原则 | 违背"组合优于继承" | 符合"组合优于继承" |
1.4 应用场景
- 支付系统适配
- 旧系统集成
- Java集合框架
1// 第三方支付接口适配2public interface PaymentProcessor {3 void processPayment(double amount);4 boolean isPaymentSuccessful();5}67// 第三方支付库8public class ThirdPartyPayment {9 public void pay(double amount) {10 System.out.println("第三方支付处理: " + amount);11 }12 13 public boolean getPaymentStatus() {14 return true;15 }16}1718// 支付适配器19public class PaymentAdapter implements PaymentProcessor {20 private ThirdPartyPayment thirdPartyPayment;21 22 public PaymentAdapter(ThirdPartyPayment thirdPartyPayment) {23 this.thirdPartyPayment = thirdPartyPayment;24 }25 26 @Override27 public void processPayment(double amount) {28 thirdPartyPayment.pay(amount);29 }30 31 @Override32 public boolean isPaymentSuccessful() {33 return thirdPartyPayment.getPaymentStatus();34 }35}3637// 使用示例38public class PaymentService {39 public void processPayment(PaymentProcessor processor, double amount) {40 processor.processPayment(amount);41 if (processor.isPaymentSuccessful()) {42 System.out.println("支付成功");43 } else {44 System.out.println("支付失败");45 }46 }47}在这个例子中,PaymentAdapter适配器将第三方支付库的接口转换为系统内部统一的PaymentProcessor接口,使得系统可以无缝集成第三方支付功能。
1// 新系统接口2public interface NewUserService {3 User getUserById(Long id);4 List<User> getAllUsers();5 void saveUser(User user);6 void deleteUser(Long id);7}89// 旧系统类10public class LegacyUserSystem {11 public UserData findUserById(int userId) {12 System.out.println("旧系统查找用户: " + userId);13 return new UserData(userId, "user" + userId);14 }15 16 public UserData[] getAllUserData() {17 System.out.println("旧系统获取所有用户");18 return new UserData[] { 19 new UserData(1, "user1"), 20 new UserData(2, "user2") 21 };22 }23 24 public void updateUserData(UserData userData) {25 System.out.println("旧系统更新用户: " + userData.getId());26 }27 28 public void removeUser(int userId) {29 System.out.println("旧系统删除用户: " + userId);30 }31}3233// 用户数据类34public class UserData {35 private int id;36 private String name;37 38 public UserData(int id, String name) {39 this.id = id;40 this.name = name;41 }42 43 public int getId() { return id; }44 public String getName() { return name; }45}4647// 新系统用户类48public class User {49 private Long id;50 private String username;51 52 // 构造函数、getter和setter53}5455// 适配器56public class LegacyUserSystemAdapter implements NewUserService {57 private LegacyUserSystem legacySystem;58 59 public LegacyUserSystemAdapter(LegacyUserSystem legacySystem) {60 this.legacySystem = legacySystem;61 }62 63 @Override64 public User getUserById(Long id) {65 UserData userData = legacySystem.findUserById(id.intValue());66 return convertToUser(userData);67 }68 69 @Override70 public List<User> getAllUsers() {71 UserData[] userDataArray = legacySystem.getAllUserData();72 List<User> users = new ArrayList<>();73 for (UserData userData : userDataArray) {74 users.add(convertToUser(userData));75 }76 return users;77 }78 79 @Override80 public void saveUser(User user) {81 legacySystem.updateUserData(convertToUserData(user));82 }83 84 @Override85 public void deleteUser(Long id) {86 legacySystem.removeUser(id.intValue());87 }88 89 private User convertToUser(UserData userData) {90 User user = new User();91 user.setId((long)userData.getId());92 user.setUsername(userData.getName());93 return user;94 }95 96 private UserData convertToUserData(User user) {97 return new UserData(user.getId().intValue(), user.getUsername());98 }99}这个示例展示了如何通过适配器模式集成旧系统的功能,将旧接口转换为新系统所期望的接口格式,并处理数据转换。
1import java.util.*;23public class CollectionAdapterExample {4 public static void main(String[] args) {5 // Arrays.asList() 是一个适配器,将数组转换为List接口6 String[] strArray = {"A", "B", "C"};7 List<String> stringList = Arrays.asList(strArray);8 9 // Collections.enumeration() 将List适配为Enumeration10 Enumeration<String> enumeration = Collections.enumeration(stringList);11 12 // Collections.list() 将Enumeration适配回List13 List<String> newList = Collections.list(enumeration);14 15 // 输出结果16 System.out.println("Original Array: " + Arrays.toString(strArray));17 System.out.println("Adapted List: " + stringList);18 System.out.println("Converted back List: " + newList);19 20 // 展示如何使用适配器进行迭代21 while (enumeration.hasMoreElements()) {22 System.out.println("Element: " + enumeration.nextElement());23 }24 }25}Java集合框架中包含多个适配器实现,如Arrays.asList()将数组适配为List接口,Collections.enumeration()将Collection适配为Enumeration接口等。这些适配器使得不同类型的集合能够协同工作。
1.5 适配器模式与其他模式的区别
| 模式 | 主要目的 | 应用情景 | 结构特点 |
|---|---|---|---|
| 适配器模式 | 接口转换 | 使不兼容的接口协同工作 | 包装一个类,转换其接口 |
| 装饰器模式 | 增加功能 | 动态添加职责 | 包装一个类,增加其行为 |
| 外观模式 | 简化接口 | 提供子系统的简化接口 | 为多个类提供一个统一接口 |
| 代理模式 | 控制访问 | 控制对对象的访问 | 包装一个类,控制对它的访问 |
- 对象适配器优先:优先使用对象适配器,它更加灵活且符合"组合优于继承"原则
- 职责清晰:适配器只负责接口转换,不应添加额外业务逻辑
- 考虑双向适配:必要时实现双向适配,允许客户端使用不同的接口
- 适度使用:避免过度使用适配器,可能导致系统难以理解
- 异常处理:适配器中要处理可能的异常转换
2. 桥接模式(Bridge)
2.1 模式定义
桥接模式将抽象部分与实现部分分离,使它们都可以独立地变化。它通过组合优于继承的设计原则,解决了多层继承带来的类爆炸问题,并提高了系统的可扩展性。
- 适用场景
- 优点
- 缺点
- 多维度变化:当一个类有两个或多个独立变化的维度时
- 避免继承爆炸:需要避免由于多层继承导致的子类数量剧增
- 运行时切换实现:需要在运行时切换不同实现
- 跨平台应用:需要实现跨平台应用,如驱动程序、图形渲染等
- 分离抽象与实现:抽象和实现可以独立变化,不会互相影响
- 提高可扩展性:可以独立扩展抽象层和实现层
- 隐藏实现细节:客户端只需关心抽象层接口
- 避免类爆炸:组合替代继承,减少类的数量
- 增加复杂度:引入额外的抽象层和间接调用
- 设计难度:需要正确识别出两个独立变化的维度
- 初始成本高:相比于单一类层次结构,初始设计成本更高
- 调试困难:组合关系比继承关系更难以理解和调试
2.2 桥接模式结构
1// 实现者接口2public interface Implementor {3 void operationImpl();4}56// 具体实现者A7public class ConcreteImplementorA implements Implementor {8 @Override9 public void operationImpl() {10 System.out.println("具体实现者A的操作");11 }12}1314// 具体实现者B15public class ConcreteImplementorB implements Implementor {16 @Override17 public void operationImpl() {18 System.out.println("具体实现者B的操作");19 }20}2122// 抽象类23public abstract class Abstraction {24 protected Implementor implementor;25 26 public Abstraction(Implementor implementor) {27 this.implementor = implementor;28 }29 30 public abstract void operation();31}3233// 精确抽象类34public class RefinedAbstraction extends Abstraction {35 public RefinedAbstraction(Implementor implementor) {36 super(implementor);37 }38 39 @Override40 public void operation() {41 System.out.println("精确抽象类的操作");42 implementor.operationImpl();43 }44}2.3 应用场景
- 图形渲染系统
- 远程控制设备
1// 渲染器接口 - 实现部分2public interface Renderer {3 void renderCircle(double x, double y, double radius);4 void renderRectangle(double x, double y, double width, double height);5}67// 矢量渲染器 - 具体实现8public class VectorRenderer implements Renderer {9 @Override10 public void renderCircle(double x, double y, double radius) {11 System.out.println("矢量渲染圆形: (" + x + ", " + y + "), 半径: " + radius);12 }13 14 @Override15 public void renderRectangle(double x, double y, double width, double height) {16 System.out.println("矢量渲染矩形: (" + x + ", " + y + "), 尺寸: " + width + "x" + height);17 }18}1920// 光栅渲染器 - 具体实现21public class RasterRenderer implements Renderer {22 @Override23 public void renderCircle(double x, double y, double radius) {24 System.out.println("光栅渲染圆形: (" + x + ", " + y + "), 半径: " + radius);25 }26 27 @Override28 public void renderRectangle(double x, double y, double width, double height) {29 System.out.println("光栅渲染矩形: (" + x + ", " + y + "), 尺寸: " + width + "x" + height);30 }31}3233// 形状类 - 抽象部分34public abstract class Shape {35 protected Renderer renderer;36 37 public Shape(Renderer renderer) {38 this.renderer = renderer;39 }40 41 public abstract void draw();42 public abstract void resize(double scale);43}4445// 圆形 - 精确抽象46public class Circle extends Shape {47 private double x, y, radius;48 49 public Circle(Renderer renderer, double x, double y, double radius) {50 super(renderer);51 this.x = x;52 this.y = y;53 this.radius = radius;54 }55 56 @Override57 public void draw() {58 renderer.renderCircle(x, y, radius);59 }60 61 @Override62 public void resize(double scale) {63 radius *= scale;64 }65}6667// 矩形 - 精确抽象68public class Rectangle extends Shape {69 private double x, y, width, height;70 71 public Rectangle(Renderer renderer, double x, double y, double width, double height) {72 super(renderer);73 this.x = x;74 this.y = y;75 this.width = width;76 this.height = height;77 }78 79 @Override80 public void draw() {81 renderer.renderRectangle(x, y, width, height);82 }83 84 @Override85 public void resize(double scale) {86 width *= scale;87 height *= scale;88 }89}9091// 客户端代码92public class BridgePatternDemo {93 public static void main(String[] args) {94 // 创建不同的渲染器95 Renderer vectorRenderer = new VectorRenderer();96 Renderer rasterRenderer = new RasterRenderer();97 98 // 使用矢量渲染器创建形状99 Shape circle1 = new Circle(vectorRenderer, 10, 10, 5);100 Shape rectangle1 = new Rectangle(vectorRenderer, 20, 20, 15, 10);101 102 // 使用光栅渲染器创建形状103 Shape circle2 = new Circle(rasterRenderer, 10, 10, 5);104 Shape rectangle2 = new Rectangle(rasterRenderer, 20, 20, 15, 10);105 106 // 绘制形状107 circle1.draw(); // 使用矢量渲染器绘制圆形108 rectangle1.draw(); // 使用矢量渲染器绘制矩形109 circle2.draw(); // 使用光栅渲染器绘制圆形110 rectangle2.draw(); // 使用光栅渲染器绘制矩形111 112 // 调整大小并再次绘制113 circle1.resize(2);114 circle1.draw();115 }116}这个示例中,我们有两个独立变化的维度:
- 形状(圆形、矩形)- 抽象部分
- 渲染方式(矢量、光栅)- 实现部分
通过桥接模式,我们可以任意组合这两个维度而不需要为每个组合创建一个类。
1// 设备接口 - 实现部分2public interface Device {3 boolean isEnabled();4 void enable();5 void disable();6 int getVolume();7 void setVolume(int volume);8 int getChannel();9 void setChannel(int channel);10}1112// 电视 - 具体实现13public class TV implements Device {14 private boolean on = false;15 private int volume = 30;16 private int channel = 1;17 18 @Override19 public boolean isEnabled() {20 return on;21 }22 23 @Override24 public void enable() {25 on = true;26 System.out.println("电视已打开");27 }28 29 @Override30 public void disable() {31 on = false;32 System.out.println("电视已关闭");33 }34 35 @Override36 public int getVolume() {37 return volume;38 }39 40 @Override41 public void setVolume(int volume) {42 this.volume = Math.min(Math.max(volume, 0), 100);43 System.out.println("电视音量设置为: " + this.volume);44 }45 46 @Override47 public int getChannel() {48 return channel;49 }50 51 @Override52 public void setChannel(int channel) {53 this.channel = channel;54 System.out.println("电视频道切换至: " + this.channel);55 }56}5758// 收音机 - 具体实现59public class Radio implements Device {60 private boolean on = false;61 private int volume = 20;62 private int channel = 88;63 64 @Override65 public boolean isEnabled() {66 return on;67 }68 69 @Override70 public void enable() {71 on = true;72 System.out.println("收音机已打开");73 }74 75 @Override76 public void disable() {77 on = false;78 System.out.println("收音机已关闭");79 }80 81 @Override82 public int getVolume() {83 return volume;84 }85 86 @Override87 public void setVolume(int volume) {88 this.volume = Math.min(Math.max(volume, 0), 100);89 System.out.println("收音机音量设置为: " + this.volume);90 }91 92 @Override93 public int getChannel() {94 return channel;95 }96 97 @Override98 public void setChannel(int channel) {99 this.channel = channel;100 System.out.println("收音机调频至: " + this.channel);101 }102}103104// 远程控制 - 抽象部分105public abstract class RemoteControl {106 protected Device device;107 108 public RemoteControl(Device device) {109 this.device = device;110 }111 112 public void togglePower() {113 if (device.isEnabled()) {114 device.disable();115 } else {116 device.enable();117 }118 }119 120 public void volumeUp() {121 device.setVolume(device.getVolume() + 10);122 }123 124 public void volumeDown() {125 device.setVolume(device.getVolume() - 10);126 }127 128 public void channelUp() {129 device.setChannel(device.getChannel() + 1);130 }131 132 public void channelDown() {133 device.setChannel(device.getChannel() - 1);134 }135}136137// 高级远程控制 - 精确抽象138public class AdvancedRemoteControl extends RemoteControl {139 public AdvancedRemoteControl(Device device) {140 super(device);141 }142 143 public void mute() {144 device.setVolume(0);145 System.out.println("已静音");146 }147 148 public void setChannel(int channel) {149 device.setChannel(channel);150 System.out.println("直接切换至频道: " + channel);151 }152}153154// 客户端代码155public class RemoteControlDemo {156 public static void main(String[] args) {157 // 创建设备158 Device tv = new TV();159 Device radio = new Radio();160 161 // 创建标准遥控器162 RemoteControl tvRemote = new RemoteControl(tv);163 tvRemote.togglePower(); // 打开电视164 tvRemote.volumeUp(); // 增加音量165 tvRemote.channelUp(); // 切换频道166 167 // 创建高级遥控器168 AdvancedRemoteControl radioAdvancedRemote = new AdvancedRemoteControl(radio);169 radioAdvancedRemote.togglePower(); // 打开收音机170 radioAdvancedRemote.setChannel(104); // 直接设置频道171 radioAdvancedRemote.mute(); // 静音172 }173}这个例子展示了桥接模式如何将远程控制(抽象部分)与设备(实现部分)分离,允许它们独立变化。我们可以有不同类型的遥控器和不同类型的设备,并且可以自由组合它们。
2.4 桥接模式与其他模式比较
| 模式 | 桥接模式 | 适配器模式 | 策略模式 | 组合模式 |
|---|---|---|---|---|
| 目的 | 分离抽象与实现 | 转换接口 | 封装算法 | 组织对象层次结构 |
| 关注点 | 处理多维度变化 | 使不兼容接口兼容 | 提供可替换算法 | 部分-整体关系 |
| 设计时机 | 设计前期 | 设计后期/集成期 | 设计中期 | 设计中期 |
| 类数量 | 中等 | 少 | 中等 | 少 |
| 复杂度 | 中高 | 低 | 中 | 中 |
2.5 实现要点
类层次结构
桥接模式创建了两个独立的类层次结构:
- 抽象层: Abstraction及其子类
- 实现层: Implementor及其实现类
设计步骤
- 识别系统中的独立变化维度
- 分离抽象部分和实现部分
- 创建抽象类及其派生类
- 创建实现接口及其实现类
- 在抽象类中维护对实现接口的引用
- 尽早设计:在系统设计初期就应考虑使用桥接模式
- 明确分离:确保抽象和实现真正独立且可以单独扩展
- 注意粒度:实现接口应该足够抽象,以允许不同的具体实现
- 考虑依赖注入:结合依赖注入框架实现桥接模式
- 处理异常情况:设计实现类时考虑可能的异常和边界情况
3. 组合模式(Composite)
3.1 模式定义
组合模式将对象组合成树形结构以表示"部分-整体"的层次结构,使得客户端对单个对象和组合对象的使用具有一致性。它允许客户端统一处理单个对象和对象组合,而无需关心其内部复杂结构。
- 适用场景
- 优点
- 缺点
- 表示部分-整体层次结构:需要表示对象的部分-整体层次结构
- 统一处理:希望客户端忽略复合对象与单个对象的差异
- 树形结构:需要处理树形结构,如文件系统、菜单系统等
- 递归组合:需要递归组合对象以形成更复杂的结构
- 简化客户端代码:客户端可以一致地处理单个对象和组合对象
- 方便添加新组件:可以方便地添加新类型的组件而不影响已有代码
- 符合开闭原则:系统更易于扩展,无需修改现有代码
- 灵活组合:可以灵活地组合对象,形成更复杂的树形结构
- 限制组件类型:很难限制组合中的组件类型
- 设计过于一般化:为了统一接口,可能会让设计变得过于一般化
- 性能考量:对组件的遍历操作可能会影响性能
- 难以确保安全性:在透明性和安全性之间需要做出权衡
3.2 组合模式结构
1// 抽象组件2public abstract class Component {3 protected String name;4 5 public Component(String name) {6 this.name = name;7 }8 9 public abstract void add(Component component);10 public abstract void remove(Component component);11 public abstract void display(int depth);12 public abstract void operation();13}1415// 叶子节点16public class Leaf extends Component {17 public Leaf(String name) {18 super(name);19 }20 21 @Override22 public void add(Component component) {23 throw new UnsupportedOperationException("叶子节点不能添加子节点");24 }25 26 @Override27 public void remove(Component component) {28 throw new UnsupportedOperationException("叶子节点不能删除子节点");29 }30 31 @Override32 public void display(int depth) {33 StringBuilder prefix = new StringBuilder();34 for (int i = 0; i < depth; i++) {35 prefix.append(" ");36 }37 System.out.println(prefix + "- " + name);38 }39 40 @Override41 public void operation() {42 System.out.println("叶子节点操作: " + name);43 }44}4546// 复合节点47public class Composite extends Component {48 private List<Component> children = new ArrayList<>();49 50 public Composite(String name) {51 super(name);52 }53 54 @Override55 public void add(Component component) {56 children.add(component);57 }58 59 @Override60 public void remove(Component component) {61 children.remove(component);62 }63 64 @Override65 public void display(int depth) {66 StringBuilder prefix = new StringBuilder();67 for (int i = 0; i < depth; i++) {68 prefix.append(" ");69 }70 System.out.println(prefix + "+ " + name);71 72 for (Component child : children) {73 child.display(depth + 1);74 }75 }76 77 @Override78 public void operation() {79 System.out.println("复合节点操作: " + name);80 for (Component child : children) {81 child.operation();82 }83 }84}3.3 组合模式变体
透明组合模式
透明组合模式在抽象组件中定义所有管理子组件的方法,包括叶子节点和组合节点,这使得客户端可以统一对待所有组件,但叶子节点必须实现一些并不适用的方法。
1public abstract class Component {2 protected String name;3 4 public Component(String name) {5 this.name = name;6 }7 8 // 所有子类都必须实现这些方法9 public abstract void add(Component component);10 public abstract void remove(Component component);11 public abstract Component getChild(int index);12 public abstract void display(int depth);13 public abstract void operation();14}安全组合模式
安全组合模式只在抽象组件中定义共同操作,而将管理子组件的方法放在组合节点类中,这样可以避免叶子节点实现不必要的方法,但客户端需要区分叶子和组合节点。
1public abstract class Component {2 protected String name;3 4 public Component(String name) {5 this.name = name;6 }7 8 // 所有组件共有的方法9 public abstract void display(int depth);10 public abstract void operation();11 12 // 叶子节点不需要实现这些方法13}1415public class Composite extends Component {16 private List<Component> children = new ArrayList<>();17 18 // 组合节点特有的方法19 public void add(Component component) {20 children.add(component);21 }22 23 public void remove(Component component) {24 children.remove(component);25 }26 27 public Component getChild(int index) {28 return children.get(index);29 }30 31 // 其他实现...32}3.4 应用场景
- 文件系统
- 菜单系统
- 组织架构
1// 文件系统项抽象2public abstract class FileSystemItem {3 protected String name;4 protected long size;5 6 public FileSystemItem(String name, long size) {7 this.name = name;8 this.size = size;9 }10 11 public String getName() {12 return name;13 }14 15 public abstract void display(int depth);16 public abstract long getSize();17 public abstract void add(FileSystemItem item);18 public abstract void remove(FileSystemItem item);19 public abstract List<FileSystemItem> getChildren();20}2122// 文件23public class File extends FileSystemItem {24 public File(String name, long size) {25 super(name, size);26 }27 28 @Override29 public void display(int depth) {30 StringBuilder prefix = new StringBuilder();31 for (int i = 0; i < depth; i++) {32 prefix.append(" ");33 }34 System.out.println(prefix + "📄 " + name + " (" + formatSize(size) + ")");35 }36 37 @Override38 public long getSize() {39 return size;40 }41 42 @Override43 public void add(FileSystemItem item) {44 throw new UnsupportedOperationException("文件不能添加子项");45 }46 47 @Override48 public void remove(FileSystemItem item) {49 throw new UnsupportedOperationException("文件不能删除子项");50 }51 52 @Override53 public List<FileSystemItem> getChildren() {54 return Collections.emptyList();55 }56 57 private String formatSize(long size) {58 if (size < 1024) {59 return size + " bytes";60 } else if (size < 1024 * 1024) {61 return String.format("%.2f KB", size / 1024.0);62 } else if (size < 1024 * 1024 * 1024) {63 return String.format("%.2f MB", size / (1024.0 * 1024));64 } else {65 return String.format("%.2f GB", size / (1024.0 * 1024 * 1024));66 }67 }68}6970// 目录71public class Directory extends FileSystemItem {72 private List<FileSystemItem> children = new ArrayList<>();73 74 public Directory(String name) {75 super(name, 0);76 }77 78 @Override79 public void display(int depth) {80 StringBuilder prefix = new StringBuilder();81 for (int i = 0; i < depth; i++) {82 prefix.append(" ");83 }84 System.out.println(prefix + "📁 " + name + " (" + formatSize(getSize()) + ")");85 86 for (FileSystemItem child : children) {87 child.display(depth + 1);88 }89 }90 91 @Override92 public long getSize() {93 long totalSize = 0;94 for (FileSystemItem child : children) {95 totalSize += child.getSize();96 }97 return totalSize;98 }99 100 @Override101 public void add(FileSystemItem item) {102 children.add(item);103 }104 105 @Override106 public void remove(FileSystemItem item) {107 children.remove(item);108 }109 110 @Override111 public List<FileSystemItem> getChildren() {112 return new ArrayList<>(children);113 }114 115 private String formatSize(long size) {116 if (size < 1024) {117 return size + " bytes";118 } else if (size < 1024 * 1024) {119 return String.format("%.2f KB", size / 1024.0);120 } else if (size < 1024 * 1024 * 1024) {121 return String.format("%.2f MB", size / (1024.0 * 1024));122 } else {123 return String.format("%.2f GB", size / (1024.0 * 1024 * 1024));124 }125 }126}127128// 客户端代码129public class FileSystemDemo {130 public static void main(String[] args) {131 // 创建根目录132 Directory root = new Directory("root");133 134 // 创建子目录和文件135 Directory docs = new Directory("documents");136 Directory pics = new Directory("pictures");137 138 File readme = new File("README.txt", 2048);139 File config = new File("config.xml", 1024);140 141 // 添加到根目录142 root.add(docs);143 root.add(pics);144 root.add(readme);145 root.add(config);146 147 // 添加子目录内容148 docs.add(new File("document1.docx", 5120));149 docs.add(new File("document2.docx", 7168));150 151 Directory vacationPics = new Directory("vacation");152 pics.add(vacationPics);153 pics.add(new File("photo1.jpg", 2048));154 155 vacationPics.add(new File("vacation1.jpg", 4096));156 vacationPics.add(new File("vacation2.jpg", 3072));157 158 // 显示文件系统结构159 root.display(0);160 161 // 计算总大小162 System.out.println("\n总大小: " + formatSize(root.getSize()));163 }164 165 private static String formatSize(long size) {166 if (size < 1024) {167 return size + " bytes";168 } else if (size < 1024 * 1024) {169 return String.format("%.2f KB", size / 1024.0);170 } else if (size < 1024 * 1024 * 1024) {171 return String.format("%.2f MB", size / (1024.0 * 1024));172 } else {173 return String.format("%.2f GB", size / (1024.0 * 1024 * 1024));174 }175 }176}这个示例模拟了一个简单的文件系统,包含目录和文件。目录可以包含其他目录和文件,而文件是叶子节点。通过组合模式,客户端代码可以统一处理文件和目录。
1// 菜单组件2public abstract class MenuComponent {3 protected String name;4 protected String description;5 6 public MenuComponent(String name, String description) {7 this.name = name;8 this.description = description;9 }10 11 public String getName() {12 return name;13 }14 15 public String getDescription() {16 return description;17 }18 19 public abstract void add(MenuComponent menuComponent);20 public abstract void remove(MenuComponent menuComponent);21 public abstract MenuComponent getChild(int i);22 public abstract void print(int depth);23 public abstract boolean isVegetarian();24 public abstract double getPrice();25}2627// 菜单项(叶子节点)28public class MenuItem extends MenuComponent {29 private boolean vegetarian;30 private double price;31 32 public MenuItem(String name, String description, boolean vegetarian, double price) {33 super(name, description);34 this.vegetarian = vegetarian;35 this.price = price;36 }37 38 @Override39 public void add(MenuComponent menuComponent) {40 throw new UnsupportedOperationException("菜单项不支持添加子菜单");41 }42 43 @Override44 public void remove(MenuComponent menuComponent) {45 throw new UnsupportedOperationException("菜单项不支持删除子菜单");46 }47 48 @Override49 public MenuComponent getChild(int i) {50 throw new UnsupportedOperationException("菜单项没有子菜单");51 }52 53 @Override54 public void print(int depth) {55 StringBuilder prefix = new StringBuilder();56 for (int i = 0; i < depth; i++) {57 prefix.append(" ");58 }59 System.out.print(prefix + "🍽️ " + getName());60 if (isVegetarian()) {61 System.out.print("(V)");62 }63 System.out.println(", " + getPrice() + "¥ -- " + getDescription());64 }65 66 @Override67 public boolean isVegetarian() {68 return vegetarian;69 }70 71 @Override72 public double getPrice() {73 return price;74 }75}7677// 菜单(组合节点)78public class Menu extends MenuComponent {79 private List<MenuComponent> menuComponents = new ArrayList<>();80 81 public Menu(String name, String description) {82 super(name, description);83 }84 85 @Override86 public void add(MenuComponent menuComponent) {87 menuComponents.add(menuComponent);88 }89 90 @Override91 public void remove(MenuComponent menuComponent) {92 menuComponents.remove(menuComponent);93 }94 95 @Override96 public MenuComponent getChild(int i) {97 return menuComponents.get(i);98 }99 100 @Override101 public void print(int depth) {102 StringBuilder prefix = new StringBuilder();103 for (int i = 0; i < depth; i++) {104 prefix.append(" ");105 }106 System.out.println(prefix + "📖 " + getName() + " -- " + getDescription());107 System.out.println(prefix + "--------------------------------");108 109 for (MenuComponent menuComponent : menuComponents) {110 menuComponent.print(depth + 1);111 }112 }113 114 @Override115 public boolean isVegetarian() {116 // 如果所有子项都是素食,则菜单为素食117 return menuComponents.stream().allMatch(MenuComponent::isVegetarian);118 }119 120 @Override121 public double getPrice() {122 // 计算菜单总价格123 return menuComponents.stream()124 .mapToDouble(MenuComponent::getPrice)125 .sum();126 }127}128129// 客户端代码130public class MenuSystemDemo {131 public static void main(String[] args) {132 // 创建主菜单133 MenuComponent mainMenu = new Menu("主菜单", "餐厅主菜单");134 135 // 创建子菜单136 MenuComponent breakfastMenu = new Menu("早餐菜单", "早餐选项");137 MenuComponent lunchMenu = new Menu("午餐菜单", "午餐选项");138 MenuComponent dinnerMenu = new Menu("晚餐菜单", "晚餐选项");139 MenuComponent dessertMenu = new Menu("甜点菜单", "甜点选项");140 141 // 添加子菜单到主菜单142 mainMenu.add(breakfastMenu);143 mainMenu.add(lunchMenu);144 mainMenu.add(dinnerMenu);145 146 // 添加菜单项到早餐菜单147 breakfastMenu.add(new MenuItem("煎饼", "香脆可口的煎饼", true, 10.0));148 breakfastMenu.add(new MenuItem("培根蛋饼", "培根和鸡蛋做成的饼", false, 15.0));149 breakfastMenu.add(new MenuItem("水果沙拉", "新鲜水果沙拉", true, 12.0));150 151 // 添加菜单项到午餐菜单152 lunchMenu.add(new MenuItem("素食汉堡", "全麦面包搭配素食汉堡", true, 20.0));153 lunchMenu.add(new MenuItem("牛肉汉堡", "牛肉汉堡配芝士和生菜", false, 25.0));154 lunchMenu.add(new MenuItem("薯条", "脆皮薯条", true, 8.0));155 156 // 添加菜单项到晚餐菜单157 dinnerMenu.add(new MenuItem("意大利面", "意大利面配番茄酱", true, 22.0));158 dinnerMenu.add(new MenuItem("牛排", "美味的牛排", false, 40.0));159 dinnerMenu.add(new MenuItem("海鲜饭", "新鲜的海鲜饭", false, 35.0));160 161 // 添加甜点菜单到晚餐菜单162 dinnerMenu.add(dessertMenu);163 164 // 添加菜单项到甜点菜单165 dessertMenu.add(new MenuItem("提拉米苏", "经典意大利甜点", true, 15.0));166 dessertMenu.add(new MenuItem("芝士蛋糕", "纽约风格芝士蛋糕", true, 18.0));167 168 // 打印整个菜单169 mainMenu.print(0);170 171 // 计算总价172 System.out.println("\n全部菜单总价: " + mainMenu.getPrice() + "¥");173 174 // 打印仅素食菜单175 System.out.println("\n素食菜单:");176 printVegetarianMenu(mainMenu, 0);177 }178 179 private static void printVegetarianMenu(MenuComponent menuComponent, int depth) {180 if (menuComponent.isVegetarian()) {181 menuComponent.print(depth);182 } else if (!(menuComponent instanceof MenuItem)) {183 // 如果是菜单,递归检查子项184 for (int i = 0; i < menuComponent.getChild(i) != null; i++) {185 try {186 MenuComponent child = menuComponent.getChild(i);187 printVegetarianMenu(child, depth + 1);188 } catch (IndexOutOfBoundsException e) {189 break;190 }191 }192 }193 }194}此示例展示了一个餐厅菜单系统,使用组合模式来表示菜单和菜单项。菜单可以包含子菜单和菜单项,而菜单项是叶子节点。客户端可以递归地遍历整个菜单结构。
1// 组织组件2public abstract class OrganizationComponent {3 protected String name;4 protected String description;5 6 public OrganizationComponent(String name, String description) {7 this.name = name;8 this.description = description;9 }10 11 public String getName() {12 return name;13 }14 15 public String getDescription() {16 return description;17 }18 19 public abstract void add(OrganizationComponent component);20 public abstract void remove(OrganizationComponent component);21 public abstract void display(int depth);22 public abstract int getEmployeeCount();23}2425// 员工(叶子节点)26public class Employee extends OrganizationComponent {27 private String position;28 private double salary;29 30 public Employee(String name, String position, double salary) {31 super(name, "员工: " + position);32 this.position = position;33 this.salary = salary;34 }35 36 public String getPosition() {37 return position;38 }39 40 public double getSalary() {41 return salary;42 }43 44 @Override45 public void add(OrganizationComponent component) {46 throw new UnsupportedOperationException("员工无法添加下属");47 }48 49 @Override50 public void remove(OrganizationComponent component) {51 throw new UnsupportedOperationException("员工无法移除下属");52 }53 54 @Override55 public void display(int depth) {56 StringBuilder prefix = new StringBuilder();57 for (int i = 0; i < depth; i++) {58 prefix.append(" ");59 }60 System.out.println(prefix + "👤 " + getName() + " - " + getPosition() + " (薪资: " + salary + ")");61 }62 63 @Override64 public int getEmployeeCount() {65 return 1;66 }67}6869// 部门(组合节点)70public class Department extends OrganizationComponent {71 private List<OrganizationComponent> subordinates = new ArrayList<>();72 73 public Department(String name, String description) {74 super(name, description);75 }76 77 @Override78 public void add(OrganizationComponent component) {79 subordinates.add(component);80 }81 82 @Override83 public void remove(OrganizationComponent component) {84 subordinates.remove(component);85 }86 87 @Override88 public void display(int depth) {89 StringBuilder prefix = new StringBuilder();90 for (int i = 0; i < depth; i++) {91 prefix.append(" ");92 }93 System.out.println(prefix + "🏢 " + getName() + " - " + getDescription() + 94 " (员工数: " + getEmployeeCount() + ")");95 96 for (OrganizationComponent component : subordinates) {97 component.display(depth + 1);98 }99 }100 101 @Override102 public int getEmployeeCount() {103 return subordinates.stream()104 .mapToInt(OrganizationComponent::getEmployeeCount)105 .sum();106 }107}108109// 客户端代码110public class OrganizationDemo {111 public static void main(String[] args) {112 // 创建组织架构113 OrganizationComponent company = new Department("科技公司", "一家创新科技公司");114 115 // 创建部门116 OrganizationComponent rd = new Department("研发部", "负责产品开发");117 OrganizationComponent marketing = new Department("市场部", "负责市场营销");118 OrganizationComponent finance = new Department("财务部", "负责财务管理");119 120 // 添加部门到公司121 company.add(rd);122 company.add(marketing);123 company.add(finance);124 125 // 创建研发子部门126 OrganizationComponent frontend = new Department("前端组", "负责前端开发");127 OrganizationComponent backend = new Department("后端组", "负责后端开发");128 129 // 添加研发子部门130 rd.add(frontend);131 rd.add(backend);132 133 // 添加员工到前端组134 frontend.add(new Employee("张三", "高级前端工程师", 20000));135 frontend.add(new Employee("李四", "前端工程师", 15000));136 frontend.add(new Employee("王五", "初级前端工程师", 10000));137 138 // 添加员工到后端组139 backend.add(new Employee("赵六", "高级后端工程师", 22000));140 backend.add(new Employee("钱七", "后端工程师", 16000));141 142 // 添加员工到市场部143 marketing.add(new Employee("孙八", "市场总监", 25000));144 marketing.add(new Employee("周九", "市场专员", 12000));145 146 // 添加员工到财务部147 finance.add(new Employee("吴十", "财务总监", 24000));148 finance.add(new Employee("郑十一", "会计", 14000));149 150 // 显示整个组织架构151 company.display(0);152 153 // 显示研发部架构154 System.out.println("\n研发部架构:");155 rd.display(0);156 157 // 显示员工总数158 System.out.println("\n公司总员工数: " + company.getEmployeeCount());159 System.out.println("研发部员工数: " + rd.getEmployeeCount());160 }161}这个例子展示了一个组织架构系统,使用组合模式来表示公司的部门层级结构。部门可以包含子部门和员工,而员工是叶子节点。通过组合模式,我们可以轻松计算任何级别的员工数量。
3.5 组合模式的实现考量
安全性与透明性的权衡
在组合模式实现中,需要权衡安全性和透明性:
-
透明性:抽象组件中定义所有操作,使客户端可以统一处理
- 优点:客户端代码简单,不需要区分叶子和组合节点
- 缺点:叶子节点必须实现不适用的方法,违反接口隔离原则
-
安全性:抽象组件只定义共同操作,管理子组件的方法只在组合节点中定义
- 优点:叶子节点不需要实现不适用的方法
- 缺点:客户端需要区分叶子和组合节点,增加类型检查
应对常见挑战
- 组件排序:为了支持组件排序,可以在Component中添加getOrder()和setOrder()方法
- 访问父组件:在需要向上遍历的场景中,可以在Component中添加对父组件的引用
- 缓存优化:为了提高性能,可以缓存组合节点的计算结果,如大小、计数等
1public class CachingComposite extends Component {2 private List<Component> children = new ArrayList<>();3 private long cachedSize = -1;4 private boolean dirty = true;5 6 @Override7 public long getSize() {8 if (dirty) {9 cachedSize = calculateSize();10 dirty = false;11 }12 return cachedSize;13 }14 15 private long calculateSize() {16 return children.stream().mapToLong(Component::getSize).sum();17 }18 19 @Override20 public void add(Component component) {21 children.add(component);22 dirty = true;23 }24 25 @Override26 public void remove(Component component) {27 children.remove(component);28 dirty = true;29 }30}- 决定安全性与透明性:根据项目需求,选择透明性或安全性实现
- 组件排序:需要排序时,添加排序相关方法和属性
- 性能优化:对频繁访问的属性使用缓存策略
- 访问控制:考虑是否需要父节点引用,以及访问控制策略
- 迭代器实现:提供遍历组合结构的迭代器,简化客户端代码
4. 装饰器模式(Decorator)
4.1 模式定义
装饰器模式动态地给一个对象添加额外的职责,是继承的一种灵活替代方案。它通过组合而非继承来扩展对象的功能,在不修改原有对象的情况下为其添加新的行为。
- 适用场景
- 优点
- 缺点
- 动态扩展功能:在不改变对象结构的前提下动态地给对象添加职责
- 避免子类爆炸:当使用继承会导致子类数量剧增时
- 运行时配置:需要在运行时动态组合多种行为
- 责任链构建:需要构建多层次的责任链
- 灵活性强:比继承更加灵活,可以动态添加或删除职责
- 遵循开闭原则:无需修改现有代码即可扩展功能
- 层次结构清晰:每个装饰器专注于单一职责
- 运行时组合:支持在运行时组合多种行为
- 小对象数量多:会创建很多小对象,增加系统复杂度
- 调试困难:多层装饰使得调试变得复杂
- 过度使用:可能导致系统难以理解和维护
- 复杂配置:组合装饰器的顺序可能会影响最终行为
4.2 装饰器模式结构
1// 组件接口2public interface Component {3 void operation();4}56// 具体组件7public class ConcreteComponent implements Component {8 @Override9 public void operation() {10 System.out.println("具体组件的操作");11 }12}1314// 抽象装饰器15public abstract class Decorator implements Component {16 protected Component component;17 18 public Decorator(Component component) {19 this.component = component;20 }21 22 @Override23 public void operation() {24 component.operation();25 }26}2728// 具体装饰器A29public class ConcreteDecoratorA extends Decorator {30 public ConcreteDecoratorA(Component component) {31 super(component);32 }33 34 @Override35 public void operation() {36 super.operation();37 addedBehavior();38 }39 40 private void addedBehavior() {41 System.out.println("装饰器A添加的行为");42 }43}4445// 具体装饰器B46public class ConcreteDecoratorB extends Decorator {47 public ConcreteDecoratorB(Component component) {48 super(component);49 }50 51 @Override52 public void operation() {53 System.out.println("装饰器B的前置处理");54 super.operation();55 System.out.println("装饰器B的后置处理");56 }57}5859// 客户端代码60public class DecoratorPatternDemo {61 public static void main(String[] args) {62 // 创建具体组件63 Component component = new ConcreteComponent();64 65 // 用装饰器A装饰66 Component decoratedA = new ConcreteDecoratorA(component);67 decoratedA.operation();68 69 System.out.println("\n==================\n");70 71 // 用装饰器B装饰72 Component decoratedB = new ConcreteDecoratorB(component);73 decoratedB.operation();74 75 System.out.println("\n==================\n");76 77 // 多层装饰:先用A装饰,再用B装饰78 Component decoratedBA = new ConcreteDecoratorB(79 new ConcreteDecoratorA(component)80 );81 decoratedBA.operation();82 }83}4.3 应用场景
- IO流装饰器
- UI组件装饰器
- 咖啡店点单系统
1// 基础输入流接口2public interface DataSource {3 String read();4 void write(String data);5}67// 具体组件:文件数据源8public class FileDataSource implements DataSource {9 private String filename;10 11 public FileDataSource(String filename) {12 this.filename = filename;13 }14 15 @Override16 public String read() {17 System.out.println("从文件读取数据: " + filename);18 return "文件内容";19 }20 21 @Override22 public void write(String data) {23 System.out.println("向文件写入数据: " + filename);24 System.out.println("内容: " + data);25 }26}2728// 抽象装饰器29public abstract class DataSourceDecorator implements DataSource {30 protected DataSource wrappee;31 32 public DataSourceDecorator(DataSource source) {33 this.wrappee = source;34 }35 36 @Override37 public String read() {38 return wrappee.read();39 }40 41 @Override42 public void write(String data) {43 wrappee.write(data);44 }45}4647// 加密装饰器48public class EncryptionDecorator extends DataSourceDecorator {49 public EncryptionDecorator(DataSource source) {50 super(source);51 }52 53 @Override54 public String read() {55 String data = super.read();56 return decrypt(data);57 }58 59 @Override60 public void write(String data) {61 super.write(encrypt(data));62 }63 64 private String encrypt(String data) {65 // 简单的加密实现(实际应用中应使用更安全的算法)66 System.out.println("应用加密");67 StringBuilder result = new StringBuilder();68 for (char c : data.toCharArray()) {69 result.append((char) (c + 1)); // 简单位移加密70 }71 return result.toString();72 }73 74 private String decrypt(String data) {75 // 解密实现76 System.out.println("应用解密");77 StringBuilder result = new StringBuilder();78 for (char c : data.toCharArray()) {79 result.append((char) (c - 1));80 }81 return result.toString();82 }83}8485// 压缩装饰器86public class CompressionDecorator extends DataSourceDecorator {87 public CompressionDecorator(DataSource source) {88 super(source);89 }90 91 @Override92 public String read() {93 String data = super.read();94 return decompress(data);95 }96 97 @Override98 public void write(String data) {99 super.write(compress(data));100 }101 102 private String compress(String data) {103 // 简单的压缩实现(实际应用中应使用真实的压缩算法)104 System.out.println("应用压缩");105 return data.replaceAll("\\s+", ""); // 简单地删除所有空白字符106 }107 108 private String decompress(String data) {109 // 解压缩实现110 System.out.println("应用解压缩");111 return data; // 简化示例112 }113}114115// 缓冲装饰器116public class BufferedDecorator extends DataSourceDecorator {117 private StringBuilder buffer = new StringBuilder();118 119 public BufferedDecorator(DataSource source) {120 super(source);121 }122 123 @Override124 public String read() {125 System.out.println("从缓冲区读取");126 if (buffer.length() == 0) {127 buffer.append(super.read());128 }129 return buffer.toString();130 }131 132 @Override133 public void write(String data) {134 System.out.println("使用缓冲写入");135 buffer.append(data);136 if (buffer.length() >= 1024) { // 假设缓冲区大小为1024137 flush();138 }139 }140 141 public void flush() {142 System.out.println("刷新缓冲区");143 super.write(buffer.toString());144 buffer.setLength(0);145 }146}147148// 客户端代码149public class IODecoratorDemo {150 public static void main(String[] args) {151 // 基础数据源152 DataSource source = new FileDataSource("data.txt");153 154 // 用加密装饰155 DataSource encrypted = new EncryptionDecorator(source);156 encrypted.write("这是加密的数据");157 System.out.println(encrypted.read());158 159 System.out.println("\n==================\n");160 161 // 多层装饰:压缩 + 加密162 DataSource compressedEncrypted = new CompressionDecorator(163 new EncryptionDecorator(source)164 );165 compressedEncrypted.write("这是压缩并加密的数据");166 System.out.println(compressedEncrypted.read());167 168 System.out.println("\n==================\n");169 170 // 多层装饰:缓冲 + 压缩 + 加密171 DataSource bufferedCompressedEncrypted = new BufferedDecorator(172 new CompressionDecorator(173 new EncryptionDecorator(source)174 )175 );176 bufferedCompressedEncrypted.write("这是缓冲、压缩并加密的数据");177 System.out.println(bufferedCompressedEncrypted.read());178 179 // 刷新缓冲区180 ((BufferedDecorator)bufferedCompressedEncrypted).flush();181 }182}Java的I/O流库是装饰器模式的经典应用。例如,你可以通过组合不同的流装饰器来创建具有多种功能的流:
1// 创建一个带缓冲、压缩和加密功能的文件输入流2InputStream input = new BufferedInputStream(3 new GZIPInputStream(4 new CipherInputStream(5 new FileInputStream("data.gz"), 6 getCipher()7 )8 )9);1// 基础UI组件接口2public interface UIComponent {3 void draw();4 String getDescription();5}67// 具体UI组件8public class TextField implements UIComponent {9 @Override10 public void draw() {11 System.out.println("绘制文本框");12 }13 14 @Override15 public String getDescription() {16 return "文本框";17 }18}1920public class Button implements UIComponent {21 @Override22 public void draw() {23 System.out.println("绘制按钮");24 }25 26 @Override27 public String getDescription() {28 return "按钮";29 }30}3132// UI装饰器抽象类33public abstract class UIDecorator implements UIComponent {34 protected UIComponent component;35 36 public UIDecorator(UIComponent component) {37 this.component = component;38 }39 40 @Override41 public void draw() {42 component.draw();43 }44 45 @Override46 public String getDescription() {47 return component.getDescription();48 }49}5051// 边框装饰器52public class BorderDecorator extends UIDecorator {53 private int borderWidth;54 private String borderColor;55 56 public BorderDecorator(UIComponent component, int borderWidth, String borderColor) {57 super(component);58 this.borderWidth = borderWidth;59 this.borderColor = borderColor;60 }61 62 @Override63 public void draw() {64 super.draw();65 System.out.println("添加边框: 宽度=" + borderWidth + ", 颜色=" + borderColor);66 }67 68 @Override69 public String getDescription() {70 return super.getDescription() + " 带" + borderColor + "边框";71 }72}7374// 滚动条装饰器75public class ScrollBarDecorator extends UIDecorator {76 private String orientation;77 78 public ScrollBarDecorator(UIComponent component, String orientation) {79 super(component);80 this.orientation = orientation;81 }82 83 @Override84 public void draw() {85 super.draw();86 System.out.println("添加" + orientation + "滚动条");87 }88 89 @Override90 public String getDescription() {91 return super.getDescription() + " 带" + orientation + "滚动条";92 }93}9495// 工具提示装饰器96public class TooltipDecorator extends UIDecorator {97 private String tooltipText;98 99 public TooltipDecorator(UIComponent component, String tooltipText) {100 super(component);101 this.tooltipText = tooltipText;102 }103 104 @Override105 public void draw() {106 super.draw();107 System.out.println("添加工具提示: " + tooltipText);108 }109 110 @Override111 public String getDescription() {112 return super.getDescription() + " 带工具提示";113 }114 115 public void showTooltip() {116 System.out.println("显示工具提示: " + tooltipText);117 }118}119120// 客户端代码121public class UIDecoratorDemo {122 public static void main(String[] args) {123 // 创建基础组件124 UIComponent textField = new TextField();125 UIComponent button = new Button();126 127 System.out.println("基础组件:");128 System.out.println("组件描述: " + textField.getDescription());129 textField.draw();130 System.out.println();131 132 // 添加边框装饰133 UIComponent borderedTextField = new BorderDecorator(textField, 2, "蓝色");134 System.out.println("装饰后的组件:");135 System.out.println("组件描述: " + borderedTextField.getDescription());136 borderedTextField.draw();137 System.out.println();138 139 // 多层装饰:边框 + 滚动条140 UIComponent scrollableBorderedTextField = new ScrollBarDecorator(141 new BorderDecorator(textField, 2, "蓝色"),142 "垂直"143 );144 System.out.println("多层装饰的组件:");145 System.out.println("组件描述: " + scrollableBorderedTextField.getDescription());146 scrollableBorderedTextField.draw();147 System.out.println();148 149 // 多层装饰:边框 + 工具提示 + 按钮150 UIComponent decoratedButton = new TooltipDecorator(151 new BorderDecorator(button, 1, "红色"),152 "点击提交表单"153 );154 System.out.println("装饰后的按钮:");155 System.out.println("组件描述: " + decoratedButton.getDescription());156 decoratedButton.draw();157 158 // 使用特定装饰器的方法159 if (decoratedButton instanceof TooltipDecorator) {160 ((TooltipDecorator) decoratedButton).showTooltip();161 }162 }163}1// 基础饮料接口2public interface Beverage {3 String getDescription();4 double cost();5}67// 具体饮料类8public class Espresso implements Beverage {9 @Override10 public String getDescription() {11 return "浓缩咖啡";12 }13 14 @Override15 public double cost() {16 return 15.0;17 }18}1920public class HouseBlend implements Beverage {21 @Override22 public String getDescription() {23 return "综合咖啡";24 }25 26 @Override27 public double cost() {28 return 12.0;29 }30}3132public class DarkRoast implements Beverage {33 @Override34 public String getDescription() {35 return "深度烘焙咖啡";36 }37 38 @Override39 public double cost() {40 return 13.0;41 }42}4344// 调料装饰器抽象类45public abstract class CondimentDecorator implements Beverage {46 protected Beverage beverage;47 48 public CondimentDecorator(Beverage beverage) {49 this.beverage = beverage;50 }51 52 @Override53 public String getDescription() {54 return beverage.getDescription();55 }56}5758// 牛奶装饰器59public class Milk extends CondimentDecorator {60 public Milk(Beverage beverage) {61 super(beverage);62 }63 64 @Override65 public String getDescription() {66 return beverage.getDescription() + ", 牛奶";67 }68 69 @Override70 public double cost() {71 return beverage.cost() + 2.0;72 }73}7475// 摩卡装饰器76public class Mocha extends CondimentDecorator {77 public Mocha(Beverage beverage) {78 super(beverage);79 }80 81 @Override82 public String getDescription() {83 return beverage.getDescription() + ", 摩卡";84 }85 86 @Override87 public double cost() {88 return beverage.cost() + 3.0;89 }90}9192// 奶泡装饰器93public class Whip extends CondimentDecorator {94 public Whip(Beverage beverage) {95 super(beverage);96 }97 98 @Override99 public String getDescription() {100 return beverage.getDescription() + ", 奶泡";101 }102 103 @Override104 public double cost() {105 return beverage.cost() + 1.5;106 }107}108109// 大豆奶装饰器110public class Soy extends CondimentDecorator {111 public Soy(Beverage beverage) {112 super(beverage);113 }114 115 @Override116 public String getDescription() {117 return beverage.getDescription() + ", 大豆奶";118 }119 120 @Override121 public double cost() {122 return beverage.cost() + 1.5;123 }124}125126// 客户端代码127public class CoffeeShopDemo {128 public static void main(String[] args) {129 // 点一杯浓缩咖啡130 Beverage beverage1 = new Espresso();131 System.out.println(beverage1.getDescription() + " ¥" + beverage1.cost());132 133 // 点一杯深度烘焙咖啡,加双倍摩卡和奶泡134 Beverage beverage2 = new DarkRoast();135 beverage2 = new Mocha(beverage2); // 加摩卡136 beverage2 = new Mocha(beverage2); // 再加一份摩卡137 beverage2 = new Whip(beverage2); // 加奶泡138 System.out.println(beverage2.getDescription() + " ¥" + beverage2.cost());139 140 // 点一杯综合咖啡,加大豆奶、摩卡和奶泡141 Beverage beverage3 = new HouseBlend();142 beverage3 = new Soy(beverage3); // 加大豆奶143 beverage3 = new Mocha(beverage3); // 加摩卡144 beverage3 = new Whip(beverage3); // 加奶泡145 System.out.println(beverage3.getDescription() + " ¥" + beverage3.cost());146 }147}4.4 装饰器模式与其他模式对比
| 特性 | 装饰器模式 | 代理模式 | 组合模式 | 策略模式 |
|---|---|---|---|---|
| 目的 | 动态添加功能 | 控制对象访问 | 组织树形结构 | 封装可替换算法 |
| 关系类型 | 包装关系 | 代表关系 | 包含关系 | 组合关系 |
| 透明性 | 对客户端透明 | 对客户端透明 | 对客户端透明 | 需要显式选择 |
| 运行时变化 | 可动态添加职责 | 通常固定 | 可动态组合 | 可动态替换 |
| 接口增强 | 可增强接口 | 通常相同接口 | 统一接口 | 完全替换实现 |
4.5 装饰器模式实现要点
透明性与非透明性装饰器
-
透明性装饰器:装饰器完全实现组件接口,对客户端透明
java1// 透明装饰器示例2Component component = new ConcreteComponent();3component = new ConcreteDecoratorA(component);4component = new ConcreteDecoratorB(component);5component.operation(); // 客户端仅使用Component接口 -
非透明性装饰器:装饰器添加了原组件没有的新方法
java1// 非透明装饰器示例2public class LoggingDecorator extends Decorator {3 public LoggingDecorator(Component component) {4 super(component);5 }67 @Override8 public void operation() {9 super.operation();10 }1112 // 新增方法13 public void enableLogging() {14 System.out.println("启用日志记录");15 }16}1718// 客户端需要知道具体装饰器类型19LoggingDecorator decorator = new LoggingDecorator(new ConcreteComponent());20decorator.operation();21decorator.enableLogging(); // 调用装饰器特有方法
装饰顺序影响
装饰器的应用顺序可能会影响最终结果,特别是对于依赖执行顺序的操作。
1// 顺序1:先压缩后加密2DataSource compressedEncrypted = new EncryptionDecorator(new CompressionDecorator(source));34// 顺序2:先加密后压缩5DataSource encryptedCompressed = new CompressionDecorator(new EncryptionDecorator(source));- 单一职责:每个装饰器只负责一个功能,保持简单明确
- 基类稳定:确保组件接口稳定,避免频繁变化
- 注意性能:装饰器嵌套层次过多可能影响性能
- 顺序敏感性:注意装饰器的应用顺序对结果的影响
- 避免过度装饰:过多的装饰层会使系统难以理解和调试
5. 外观模式(Facade)
5.1 模式定义
外观模式为子系统中的一组接口提供一个统一的高层接口,使得子系统更容易使用。它定义了一个高层接口,这个接口使得子系统更加容易访问,降低了客户端与子系统之间的耦合度。
- 适用场景
- 优点
- 缺点
- 复杂子系统:当需要简化复杂子系统的访问时
- 分层系统:在分层系统中,使用外观定义每层的入口点
- 子系统独立:将客户端代码与子系统组件解耦
- 统一接口:为子系统提供一个简单的统一接口
- 简化接口:为复杂子系统提供简单的接口
- 降低耦合度:减少客户端与子系统组件的直接依赖
- 隐藏实现:隐藏子系统的实现细节
- 提高安全性:通过外观控制对子系统的访问
- 上帝对象:可能会变成上帝对象,承担过多责任
- 性能开销:引入额外的间接层,可能增加性能开销
- 适应性差:一旦定义,不易适应子系统的变化
- 抽象程度:为提供简单接口可能损失一些灵活性
5.2 外观模式结构
1// 子系统A2public class SubsystemA {3 public void operationA() {4 System.out.println("子系统A的操作");5 }6}78// 子系统B9public class SubsystemB {10 public void operationB() {11 System.out.println("子系统B的操作");12 }13}1415// 子系统C16public class SubsystemC {17 public void operationC() {18 System.out.println("子系统C的操作");19 }20}2122// 外观类23public class Facade {24 private SubsystemA subsystemA;25 private SubsystemB subsystemB;26 private SubsystemC subsystemC;27 28 public Facade() {29 this.subsystemA = new SubsystemA();30 this.subsystemB = new SubsystemB();31 this.subsystemC = new SubsystemC();32 }33 34 // 提供简化的接口35 public void operation() {36 System.out.println("=== 外观开始组合操作 ===");37 subsystemA.operationA();38 subsystemB.operationB();39 subsystemC.operationC();40 System.out.println("=== 外观结束组合操作 ===");41 }42 43 // 提供特定的操作组合44 public void operationAB() {45 System.out.println("=== 外观开始AB操作 ===");46 subsystemA.operationA();47 subsystemB.operationB();48 System.out.println("=== 外观结束AB操作 ===");49 }50 51 // 提供另一种操作组合52 public void operationBC() {53 System.out.println("=== 外观开始BC操作 ===");54 subsystemB.operationB();55 subsystemC.operationC();56 System.out.println("=== 外观结束BC操作 ===");57 }58}5960// 客户端代码61public class FacadePatternDemo {62 public static void main(String[] args) {63 // 使用外观64 Facade facade = new Facade();65 facade.operation();66 67 System.out.println("\n执行特定组合操作:");68 facade.operationAB();69 facade.operationBC();70 71 // 不使用外观(直接与子系统交互)72 System.out.println("\n不使用外观模式:");73 SubsystemA subsystemA = new SubsystemA();74 SubsystemB subsystemB = new SubsystemB();75 SubsystemC subsystemC = new SubsystemC();76 77 subsystemA.operationA();78 subsystemB.operationB();79 subsystemC.operationC();80 }81}5.3 外观模式变体
多层外观
在大型系统中,可能需要多个外观类形成层次结构,每个外观负责系统的不同部分。
1// 第一层外观2public class FirstLevelFacade {3 private SubsystemA subsystemA;4 private SubsystemB subsystemB;5 6 public FirstLevelFacade() {7 this.subsystemA = new SubsystemA();8 this.subsystemB = new SubsystemB();9 }10 11 public void operation() {12 subsystemA.operationA();13 subsystemB.operationB();14 }15}1617// 第二层外观18public class SecondLevelFacade {19 private FirstLevelFacade firstLevelFacade;20 private SubsystemC subsystemC;21 22 public SecondLevelFacade() {23 this.firstLevelFacade = new FirstLevelFacade();24 this.subsystemC = new SubsystemC();25 }26 27 public void operation() {28 firstLevelFacade.operation();29 subsystemC.operationC();30 }31}子系统外观
每个子系统可以有自己的外观类,以简化对该子系统的访问。
1// 数据库子系统外观2public class DatabaseFacade {3 private Connection connection;4 private Statement statement;5 private ResultSet resultSet;6 7 public List<String> executeQuery(String query) {8 List<String> results = new ArrayList<>();9 try {10 // 简化数据库操作11 connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db", "user", "password");12 statement = connection.createStatement();13 resultSet = statement.executeQuery(query);14 15 while (resultSet.next()) {16 results.add(resultSet.getString(1));17 }18 } catch (SQLException e) {19 e.printStackTrace();20 } finally {21 // 关闭资源22 close();23 }24 return results;25 }26 27 private void close() {28 try {29 if (resultSet != null) resultSet.close();30 if (statement != null) statement.close();31 if (connection != null) connection.close();32 } catch (SQLException e) {33 e.printStackTrace();34 }35 }36}5.4 应用场景
- 家庭影院系统
- 编译器外观
- API网关
1// 家庭影院组件2public class Amplifier {3 public void on() {4 System.out.println("放大器已开启");5 }6 7 public void setVolume(int level) {8 System.out.println("设置音量为 " + level);9 }10 11 public void off() {12 System.out.println("放大器已关闭");13 }14}1516public class DvdPlayer {17 public void on() {18 System.out.println("DVD播放器已开启");19 }20 21 public void play(String movie) {22 System.out.println("播放电影:" + movie);23 }24 25 public void stop() {26 System.out.println("DVD播放停止");27 }28 29 public void eject() {30 System.out.println("DVD弹出");31 }32 33 public void off() {34 System.out.println("DVD播放器已关闭");35 }36}3738public class Projector {39 public void on() {40 System.out.println("投影仪已开启");41 }42 43 public void wideScreenMode() {44 System.out.println("投影仪设置为宽屏模式");45 }46 47 public void off() {48 System.out.println("投影仪已关闭");49 }50}5152public class TheaterLights {53 public void dim(int level) {54 System.out.println("将灯光调暗至 " + level + "%");55 }56 57 public void on() {58 System.out.println("灯光已开启");59 }60}6162public class Screen {63 public void down() {64 System.out.println("屏幕降下");65 }66 67 public void up() {68 System.out.println("屏幕升起");69 }70}7172public class PopcornPopper {73 public void on() {74 System.out.println("爆米花机已开启");75 }76 77 public void pop() {78 System.out.println("爆米花机开始制作爆米花");79 }80 81 public void off() {82 System.out.println("爆米花机已关闭");83 }84}8586// 家庭影院外观87public class HomeTheaterFacade {88 private Amplifier amplifier;89 private DvdPlayer dvdPlayer;90 private Projector projector;91 private TheaterLights lights;92 private Screen screen;93 private PopcornPopper popper;94 95 public HomeTheaterFacade(96 Amplifier amplifier,97 DvdPlayer dvdPlayer,98 Projector projector,99 TheaterLights lights,100 Screen screen,101 PopcornPopper popper) {102 this.amplifier = amplifier;103 this.dvdPlayer = dvdPlayer;104 this.projector = projector;105 this.lights = lights;106 this.screen = screen;107 this.popper = popper;108 }109 110 public void watchMovie(String movie) {111 System.out.println("=== 准备观看电影 ===");112 popper.on();113 popper.pop();114 lights.dim(10);115 screen.down();116 projector.on();117 projector.wideScreenMode();118 amplifier.on();119 amplifier.setVolume(5);120 dvdPlayer.on();121 dvdPlayer.play(movie);122 System.out.println("=== 开始观看电影:" + movie + " ===");123 }124 125 public void endMovie() {126 System.out.println("=== 结束观看电影 ===");127 popper.off();128 lights.on();129 screen.up();130 projector.off();131 amplifier.off();132 dvdPlayer.stop();133 dvdPlayer.eject();134 dvdPlayer.off();135 System.out.println("=== 家庭影院系统已关闭 ===");136 }137}138139// 客户端代码140public class HomeTheaterDemo {141 public static void main(String[] args) {142 // 创建组件143 Amplifier amplifier = new Amplifier();144 DvdPlayer dvdPlayer = new DvdPlayer();145 Projector projector = new Projector();146 TheaterLights lights = new TheaterLights();147 Screen screen = new Screen();148 PopcornPopper popper = new PopcornPopper();149 150 // 创建外观151 HomeTheaterFacade homeTheater = new HomeTheaterFacade(152 amplifier, dvdPlayer, projector, lights, screen, popper);153 154 // 使用外观简化操作155 homeTheater.watchMovie("《复仇者联盟》");156 157 System.out.println("\n电影播放中...\n");158 159 homeTheater.endMovie();160 }161}1// 编译器子系统2public class Scanner {3 public void scan(String sourceCode) {4 System.out.println("词法分析:扫描源代码");5 }6 7 public List<String> getTokens() {8 List<String> tokens = new ArrayList<>();9 // 添加一些示例token10 tokens.add("int");11 tokens.add("main");12 tokens.add("(");13 tokens.add(")");14 return tokens;15 }16}1718public class Parser {19 public void parse(List<String> tokens) {20 System.out.println("语法分析:解析token列表");21 System.out.println("构建语法树");22 }23 24 public Object getSyntaxTree() {25 return new Object(); // 简化的语法树26 }27}2829public class SemanticAnalyzer {30 public void analyze(Object syntaxTree) {31 System.out.println("语义分析:检查类型和作用域");32 }33 34 public Object getAnnotatedTree() {35 return new Object(); // 简化的带注释语法树36 }37}3839public class CodeGenerator {40 public void generate(Object annotatedTree) {41 System.out.println("代码生成:生成目标代码");42 }43 44 public String getObjectCode() {45 return "010101..."; // 简化的机器码46 }47}4849public class Optimizer {50 public void optimize(Object annotatedTree) {51 System.out.println("优化:执行代码优化");52 }53 54 public Object getOptimizedTree() {55 return new Object(); // 简化的优化后语法树56 }57}5859// 编译器外观60public class CompilerFacade {61 private Scanner scanner;62 private Parser parser;63 private SemanticAnalyzer semanticAnalyzer;64 private Optimizer optimizer;65 private CodeGenerator codeGenerator;66 67 public CompilerFacade() {68 this.scanner = new Scanner();69 this.parser = new Parser();70 this.semanticAnalyzer = new SemanticAnalyzer();71 this.optimizer = new Optimizer();72 this.codeGenerator = new CodeGenerator();73 }74 75 public String compile(String sourceCode) {76 System.out.println("=== 开始编译过程 ===");77 78 // 1. 词法分析79 scanner.scan(sourceCode);80 List<String> tokens = scanner.getTokens();81 82 // 2. 语法分析83 parser.parse(tokens);84 Object syntaxTree = parser.getSyntaxTree();85 86 // 3. 语义分析87 semanticAnalyzer.analyze(syntaxTree);88 Object annotatedTree = semanticAnalyzer.getAnnotatedTree();89 90 // 4. 优化91 optimizer.optimize(annotatedTree);92 Object optimizedTree = optimizer.getOptimizedTree();93 94 // 5. 代码生成95 codeGenerator.generate(optimizedTree);96 String objectCode = codeGenerator.getObjectCode();97 98 System.out.println("=== 编译过程完成 ===");99 return objectCode;100 }101 102 // 只执行前端编译(词法、语法、语义分析)103 public Object frontEndCompile(String sourceCode) {104 System.out.println("=== 开始前端编译 ===");105 106 scanner.scan(sourceCode);107 List<String> tokens = scanner.getTokens();108 109 parser.parse(tokens);110 Object syntaxTree = parser.getSyntaxTree();111 112 semanticAnalyzer.analyze(syntaxTree);113 Object annotatedTree = semanticAnalyzer.getAnnotatedTree();114 115 System.out.println("=== 前端编译完成 ===");116 return annotatedTree;117 }118}119120// 客户端代码121public class CompilerDemo {122 public static void main(String[] args) {123 CompilerFacade compiler = new CompilerFacade();124 125 String sourceCode = "int main() { return 0; }";126 127 // 使用外观进行完整编译128 String objectCode = compiler.compile(sourceCode);129 System.out.println("生成的目标代码:" + objectCode);130 131 // 使用外观只进行前端编译132 Object ast = compiler.frontEndCompile(sourceCode);133 System.out.println("生成的抽象语法树:" + ast);134 }135}1// 微服务组件2public class AuthService {3 public boolean authenticate(String token) {4 System.out.println("认证服务:验证token");5 return token != null && !token.isEmpty();6 }7 8 public String getUserId(String token) {9 return "user123";10 }11}1213public class UserService {14 public String getUserInfo(String userId) {15 System.out.println("用户服务:获取用户信息,用户ID = " + userId);16 return "{'id':'" + userId + "','name':'张三','email':'zhangsan@example.com'}";17 }18}1920public class ProductService {21 public String getProducts() {22 System.out.println("产品服务:获取产品列表");23 return "[{'id':'p1','name':'笔记本电脑'},{'id':'p2','name':'智能手机'}]";24 }25 26 public String getProductDetails(String productId) {27 System.out.println("产品服务:获取产品详情,产品ID = " + productId);28 return "{'id':'" + productId + "','name':'笔记本电脑','price':5999,'stock':100}";29 }30}3132public class OrderService {33 public String createOrder(String userId, String productId) {34 System.out.println("订单服务:创建订单,用户ID = " + userId + ", 产品ID = " + productId);35 return "{'orderId':'o12345','userId':'" + userId + "','productId':'" + productId + "','status':'created'}";36 }37 38 public String getOrderHistory(String userId) {39 System.out.println("订单服务:获取订单历史,用户ID = " + userId);40 return "[{'orderId':'o12345','productId':'p1','date':'2023-01-01'},{'orderId':'o12346','productId':'p2','date':'2023-02-01'}]";41 }42}4344// API网关外观45public class APIGateway {46 private AuthService authService;47 private UserService userService;48 private ProductService productService;49 private OrderService orderService;50 51 public APIGateway() {52 this.authService = new AuthService();53 this.userService = new UserService();54 this.productService = new ProductService();55 this.orderService = new OrderService();56 }57 58 // 统一的API入口点59 public String handleRequest(String path, String method, String token, Map<String, String> params) {60 System.out.println("API网关:接收请求 " + method + " " + path);61 62 // 身份验证(除了产品列表外的所有请求)63 if (!path.equals("/products") && !authService.authenticate(token)) {64 return "{'error':'unauthorized','message':'身份验证失败'}";65 }66 67 // 路由请求到相应的服务68 switch (path) {69 case "/user":70 String userId = authService.getUserId(token);71 return userService.getUserInfo(userId);72 73 case "/products":74 return productService.getProducts();75 76 case "/product":77 String productId = params.get("id");78 if (productId == null) {79 return "{'error':'bad_request','message':'缺少产品ID'}";80 }81 return productService.getProductDetails(productId);82 83 case "/orders":84 userId = authService.getUserId(token);85 return orderService.getOrderHistory(userId);86 87 case "/order/create":88 if (method.equals("POST")) {89 userId = authService.getUserId(token);90 productId = params.get("productId");91 if (productId == null) {92 return "{'error':'bad_request','message':'缺少产品ID'}";93 }94 return orderService.createOrder(userId, productId);95 } else {96 return "{'error':'method_not_allowed','message':'不支持的HTTP方法'}";97 }98 99 default:100 return "{'error':'not_found','message':'资源不存在'}";101 }102 }103}104105// 客户端代码106public class APIGatewayDemo {107 public static void main(String[] args) {108 APIGateway gateway = new APIGateway();109 110 // 模拟请求111 String token = "valid_token";112 Map<String, String> params = new HashMap<>();113 114 // 1. 获取产品列表(无需认证)115 String productsResponse = gateway.handleRequest("/products", "GET", null, params);116 System.out.println("产品列表响应:" + productsResponse);117 118 // 2. 获取产品详情119 params.put("id", "p1");120 String productResponse = gateway.handleRequest("/product", "GET", token, params);121 System.out.println("产品详情响应:" + productResponse);122 123 // 3. 获取用户信息124 String userResponse = gateway.handleRequest("/user", "GET", token, new HashMap<>());125 System.out.println("用户信息响应:" + userResponse);126 127 // 4. 创建订单128 params.clear();129 params.put("productId", "p1");130 String orderResponse = gateway.handleRequest("/order/create", "POST", token, params);131 System.out.println("创建订单响应:" + orderResponse);132 133 // 5. 获取订单历史134 String ordersResponse = gateway.handleRequest("/orders", "GET", token, new HashMap<>());135 System.out.println("订单历史响应:" + ordersResponse);136 137 // 6. 无效请求138 String invalidResponse = gateway.handleRequest("/invalid", "GET", token, new HashMap<>());139 System.out.println("无效请求响应:" + invalidResponse);140 }141}5.5 外观模式与其他模式的关系
| 模式 | 外观模式 | 适配器模式 | 中介者模式 | 代理模式 |
|---|---|---|---|---|
| 目的 | 简化接口 | 转换接口 | 解耦对象 | 控制访问 |
| 适用范围 | 多个对象 | 单个对象 | 多个对象 | 单个对象 |
| 交互方式 | 单向调用 | 适配调用 | 协调通信 | 透明代理 |
| 侧重点 | 简化复杂性 | 解决不兼容 | 减少耦合 | 增加控制 |
| 时机 | 子系统设计后期 | 已有接口不兼容时 | 系统设计初期 | 扩展现有对象 |
- 接口设计:外观应提供简单、直观的接口,隐藏子系统复杂性
- 子系统独立:保持子系统独立,不要让外观成为它们的唯一入口
- 多层外观:在大型系统中,考虑使用多层外观分解复杂性
- 避免上帝对象:不要让外观承担过多责任,保持单一职责
- 组合使用:外观可以与其他模式组合使用,如适配器和策略模式
6. 享元模式(Flyweight)
6.1 模式定义
享元模式运用共享技术来高效地支持大量细粒度对象。它通过共享相同状态的对象实例,减少内存消耗并提高性能,尤其在需要创建大量相似对象的系统中非常有用。
- 适用场景
- 优点
- 缺点
- 大量相似对象:系统中存在大量相似的对象,造成内存开销
- 外部状态分离:对象的状态可以分为内部状态和外部状态
- 对象标识不重要:对象的标识对客户端不重要
- 常量池:需要缓存和重用对象实例,如字符串常量池
- 减少内存消耗:共享对象减少对象实例数量
- 降低创建成本:避免重复创建相同对象
- 提高应用性能:减少内存使用和垃圾回收压力
- 降低系统复杂度:将内部状态和外部状态分离
- 复杂性增加:需要区分内部状态和外部状态
- 线程安全:共享对象可能引发线程安全问题
- 上下文切换:在不同上下文间切换共享对象可能带来开销
- 调试困难:共享对象使得系统行为难以理解和调试
6.2 享元模式结构
1// 享元接口2public interface Flyweight {3 void operation(String extrinsicState);4}56// 具体享元7public class ConcreteFlyweight implements Flyweight {8 private final String intrinsicState; // 内部状态,不会改变且可共享9 10 public ConcreteFlyweight(String intrinsicState) {11 this.intrinsicState = intrinsicState;12 System.out.println("创建具体享元: " + intrinsicState);13 }14 15 @Override16 public void operation(String extrinsicState) {17 System.out.println("具体享元 [" + intrinsicState + "] 执行操作,外部状态: " + extrinsicState);18 }19}2021// 不共享的具体享元22public class UnsharedConcreteFlyweight implements Flyweight {23 private final String allState; // 所有状态,不共享24 25 public UnsharedConcreteFlyweight(String allState) {26 this.allState = allState;27 System.out.println("创建不共享的具体享元: " + allState);28 }29 30 @Override31 public void operation(String extrinsicState) {32 System.out.println("不共享的具体享元 [" + allState + "] 执行操作,外部状态: " + extrinsicState);33 }34}3536// 享元工厂37public class FlyweightFactory {38 private final Map<String, Flyweight> flyweights = new HashMap<>();39 40 // 获取享元对象,如果存在则共享,否则创建新的41 public Flyweight getFlyweight(String key) {42 return flyweights.computeIfAbsent(key, ConcreteFlyweight::new);43 }44 45 // 获取不共享的享元对象46 public Flyweight getUnsharedFlyweight(String key) {47 return new UnsharedConcreteFlyweight(key);48 }49 50 // 获取享元对象数量51 public int getFlyweightCount() {52 return flyweights.size();53 }54 55 // 获取所有享元对象的内部状态56 public List<String> getFlyweightKeys() {57 return new ArrayList<>(flyweights.keySet());58 }59}6061// 客户端代码62public class FlyweightPatternDemo {63 public static void main(String[] args) {64 FlyweightFactory factory = new FlyweightFactory();65 66 // 获取共享的享元对象67 Flyweight fw1 = factory.getFlyweight("A");68 fw1.operation("First call");69 70 Flyweight fw2 = factory.getFlyweight("B");71 fw2.operation("Second call");72 73 Flyweight fw3 = factory.getFlyweight("A"); // 重用已存在的对象74 fw3.operation("Third call");75 76 // 获取不共享的享元对象77 Flyweight unsharedFw = factory.getUnsharedFlyweight("X");78 unsharedFw.operation("Unshared call");79 80 // 显示共享对象的数量81 System.out.println("共享的享元对象数量: " + factory.getFlyweightCount());82 System.out.println("共享的享元对象键: " + factory.getFlyweightKeys());83 }84}6.3 内部状态与外部状态
享元模式的核心在于将对象的状态分为内部状态和外部状态:
-
内部状态(Intrinsic State)
- 存储在享元对象内部
- 可以被多个对象共享
- 独立于对象的使用场景
- 通常是不可变的
-
外部状态(Extrinsic State)
- 不存储在享元对象内部
- 由客户端代码提供
- 根据使用场景而变化
- 不可共享
1// 带有清晰分离的内部和外部状态的享元2public class CharacterFlyweight {3 // 内部状态 - 不可变且可共享4 private final char character;5 private final String fontFamily;6 private final int fontSize;7 8 public CharacterFlyweight(char character, String fontFamily, int fontSize) {9 this.character = character;10 this.fontFamily = fontFamily;11 this.fontSize = fontSize;12 }13 14 // 使用外部状态渲染字符15 public void render(int x, int y, String color) {16 System.out.printf("渲染字符 '%c' 在位置(%d, %d),使用字体 %s,大小 %d,颜色 %s%n",17 character, x, y, fontFamily, fontSize, color);18 }19 20 // 重写equals和hashCode方法,用于在Map中正确地识别相同的享元21 @Override22 public boolean equals(Object o) {23 if (this == o) return true;24 if (o == null || getClass() != o.getClass()) return false;25 CharacterFlyweight that = (CharacterFlyweight) o;26 return character == that.character &&27 fontSize == that.fontSize &&28 Objects.equals(fontFamily, that.fontFamily);29 }30 31 @Override32 public int hashCode() {33 return Objects.hash(character, fontFamily, fontSize);34 }35}6.4 应用场景
- 文本编辑器
- 游戏粒子系统
- 数据库连接池
1// 字符享元2public class CharacterFlyweight {3 private final char symbol;4 private final String fontName;5 private final int fontSize;6 7 public CharacterFlyweight(char symbol, String fontName, int fontSize) {8 this.symbol = symbol;9 this.fontName = fontName;10 this.fontSize = fontSize;11 }12 13 public void display(int x, int y, String color) {14 System.out.printf("显示字符 '%c' 在位置(%d, %d),使用字体 %s,大小 %d,颜色 %s%n",15 symbol, x, y, fontName, fontSize, color);16 }17}1819// 字符享元工厂20public class CharacterFlyweightFactory {21 private final Map<String, CharacterFlyweight> characters = new HashMap<>();22 23 public CharacterFlyweight getCharacter(char symbol, String fontName, int fontSize) {24 String key = String.format("%c-%s-%d", symbol, fontName, fontSize);25 26 return characters.computeIfAbsent(key, k -> 27 new CharacterFlyweight(symbol, fontName, fontSize)28 );29 }30 31 public int getCharacterCount() {32 return characters.size();33 }34}3536// 文本文档类,使用享元模式37public class TextDocument {38 private final CharacterFlyweightFactory factory;39 private final List<Character> characters = new ArrayList<>();40 private final List<Integer> xCoords = new ArrayList<>();41 private final List<Integer> yCoords = new ArrayList<>();42 private final List<String> colors = new ArrayList<>();43 44 public TextDocument(CharacterFlyweightFactory factory) {45 this.factory = factory;46 }47 48 // 添加字符到文档49 public void addCharacter(char symbol, int x, int y, String fontName, int fontSize, String color) {50 CharacterFlyweight characterFlyweight = factory.getCharacter(symbol, fontName, fontSize);51 52 // 存储外部状态53 characters.add(symbol);54 xCoords.add(x);55 yCoords.add(y);56 colors.add(color);57 }58 59 // 渲染整个文档60 public void render() {61 System.out.println("渲染文档...");62 for (int i = 0; i < characters.size(); i++) {63 char symbol = characters.get(i);64 int x = xCoords.get(i);65 int y = yCoords.get(i);66 String color = colors.get(i);67 68 // 获取对应的享元对象69 CharacterFlyweight characterFlyweight = factory.getCharacter(symbol, "Arial", 12);70 characterFlyweight.display(x, y, color);71 }72 }73}7475// 客户端代码76public class TextEditorDemo {77 public static void main(String[] args) {78 CharacterFlyweightFactory factory = new CharacterFlyweightFactory();79 TextDocument document = new TextDocument(factory);80 81 // 添加文本到文档,每个字符有自己的位置和颜色(外部状态)82 String text = "Hello, World!";83 int x = 10;84 int y = 20;85 86 for (int i = 0; i < text.length(); i++) {87 char c = text.charAt(i);88 String color = (i % 2 == 0) ? "black" : "blue"; // 交替颜色89 document.addCharacter(c, x, y, "Arial", 12, color);90 x += 10; // 移动到下一个位置91 }92 93 // 再次添加相同文本,但位置和颜色不同94 x = 10;95 y = 40;96 97 for (int i = 0; i < text.length(); i++) {98 char c = text.charAt(i);99 String color = (i % 2 == 0) ? "red" : "green"; // 交替颜色100 document.addCharacter(c, x, y, "Arial", 12, color);101 x += 10; // 移动到下一个位置102 }103 104 // 渲染文档105 document.render();106 107 // 显示享元对象数量108 System.out.println("\n共创建了 " + factory.getCharacterCount() + " 个享元对象");109 // 虽然有26个字符被渲染,但只创建了不同字符的享元对象110 }111}1// 粒子类型 - 内部状态2public class ParticleType {3 private final String texture; // 粒子纹理4 private final int animationFrames; // 动画帧数5 private final boolean collidable; // 是否可碰撞6 7 public ParticleType(String texture, int animationFrames, boolean collidable) {8 this.texture = texture;9 this.animationFrames = animationFrames;10 this.collidable = collidable;11 System.out.println("创建新的粒子类型: " + texture);12 }13 14 public void render(float x, float y, float scale, float rotation, String color) {15 System.out.printf("渲染粒子:纹理=%s, 位置=(%.1f, %.1f), 缩放=%.1f, 旋转=%.1f, 颜色=%s%n",16 texture, x, y, scale, rotation, color);17 }18 19 public boolean isCollidable() {20 return collidable;21 }22}2324// 粒子类型工厂25public class ParticleTypeFactory {26 private final Map<String, ParticleType> particleTypes = new HashMap<>();27 28 public ParticleType getParticleType(String texture, int animationFrames, boolean collidable) {29 String key = String.format("%s-%d-%b", texture, animationFrames, collidable);30 31 return particleTypes.computeIfAbsent(key, k -> 32 new ParticleType(texture, animationFrames, collidable)33 );34 }35 36 public int getParticleTypeCount() {37 return particleTypes.size();38 }39}4041// 粒子 - 包含内部状态引用和外部状态42public class Particle {43 // 引用到共享的粒子类型(内部状态)44 private final ParticleType type;45 46 // 外部状态 - 每个粒子实例特有的47 private float x;48 private float y;49 private float scale;50 private float rotation;51 private String color;52 private float velocityX;53 private float velocityY;54 55 public Particle(ParticleType type, float x, float y) {56 this.type = type;57 this.x = x;58 this.y = y;59 this.scale = 1.0f;60 this.rotation = 0.0f;61 this.color = "white";62 this.velocityX = 0.0f;63 this.velocityY = 0.0f;64 }65 66 // 设置速度67 public void setVelocity(float vx, float vy) {68 this.velocityX = vx;69 this.velocityY = vy;70 }71 72 // 设置颜色73 public void setColor(String color) {74 this.color = color;75 }76 77 // 更新粒子状态78 public void update(float deltaTime) {79 x += velocityX * deltaTime;80 y += velocityY * deltaTime;81 rotation += 0.1f * deltaTime; // 简单的旋转82 }83 84 // 渲染粒子85 public void render() {86 type.render(x, y, scale, rotation, color);87 }88 89 // 检查是否可碰撞90 public boolean isCollidable() {91 return type.isCollidable();92 }93}9495// 粒子系统96public class ParticleSystem {97 private final ParticleTypeFactory typeFactory;98 private final List<Particle> activeParticles = new ArrayList<>();99 private final Random random = new Random();100 101 public ParticleSystem() {102 this.typeFactory = new ParticleTypeFactory();103 }104 105 // 创建爆炸效果106 public void createExplosion(float x, float y, int particleCount) {107 System.out.println("创建爆炸效果,位置: (" + x + ", " + y + ")");108 ParticleType fireType = typeFactory.getParticleType("fire", 8, false);109 ParticleType smokeType = typeFactory.getParticleType("smoke", 5, false);110 111 // 创建火焰粒子112 for (int i = 0; i < particleCount / 2; i++) {113 float offsetX = random.nextFloat() * 10 - 5;114 float offsetY = random.nextFloat() * 10 - 5;115 116 Particle particle = new Particle(fireType, x + offsetX, y + offsetY);117 particle.setVelocity(118 (random.nextFloat() - 0.5f) * 20,119 (random.nextFloat() - 0.5f) * 20120 );121 particle.setColor("orange");122 activeParticles.add(particle);123 }124 125 // 创建烟雾粒子126 for (int i = 0; i < particleCount / 2; i++) {127 float offsetX = random.nextFloat() * 15 - 7.5f;128 float offsetY = random.nextFloat() * 15 - 7.5f;129 130 Particle particle = new Particle(smokeType, x + offsetX, y + offsetY);131 particle.setVelocity(132 (random.nextFloat() - 0.5f) * 10,133 (random.nextFloat() - 0.3f) * 15 - 5 // 烟雾主要向上移动134 );135 particle.setColor("gray");136 activeParticles.add(particle);137 }138 }139 140 // 创建雨滴效果141 public void createRainEffect(int width, int height, int particleCount) {142 System.out.println("创建雨滴效果");143 ParticleType rainType = typeFactory.getParticleType("rain", 1, false);144 145 for (int i = 0; i < particleCount; i++) {146 float x = random.nextFloat() * width;147 float y = random.nextFloat() * height;148 149 Particle particle = new Particle(rainType, x, y);150 particle.setVelocity(0, 30 + random.nextFloat() * 20); // 雨滴向下移动151 particle.setColor("blue");152 activeParticles.add(particle);153 }154 }155 156 // 更新所有粒子157 public void update(float deltaTime) {158 Iterator<Particle> iterator = activeParticles.iterator();159 while (iterator.hasNext()) {160 Particle particle = iterator.next();161 particle.update(deltaTime);162 163 // 简单的生命周期管理 - 如果粒子移出屏幕则移除164 if (particle.getX() < -100 || particle.getX() > 800 || 165 particle.getY() < -100 || particle.getY() > 600) {166 iterator.remove();167 }168 }169 }170 171 // 渲染所有粒子172 public void render() {173 for (Particle particle : activeParticles) {174 particle.render();175 }176 }177 178 // 获取活跃粒子数量179 public int getActiveParticleCount() {180 return activeParticles.size();181 }182 183 // 获取粒子类型数量184 public int getParticleTypeCount() {185 return typeFactory.getParticleTypeCount();186 }187}188189// 客户端代码190public class GameDemo {191 public static void main(String[] args) {192 ParticleSystem particleSystem = new ParticleSystem();193 194 // 创建多个爆炸效果195 particleSystem.createExplosion(100, 100, 50);196 particleSystem.createExplosion(400, 300, 30);197 particleSystem.createExplosion(200, 400, 40);198 199 // 创建雨滴效果200 particleSystem.createRainEffect(800, 600, 100);201 202 // 模拟游戏循环203 System.out.println("\n开始游戏循环...");204 float deltaTime = 0.016f; // 约60帧每秒205 206 for (int frame = 0; frame < 3; frame++) {207 System.out.println("\n=== 帧 " + frame + " ===");208 particleSystem.update(deltaTime);209 particleSystem.render();210 211 System.out.println("活跃粒子数: " + particleSystem.getActiveParticleCount());212 System.out.println("粒子类型数: " + particleSystem.getParticleTypeCount());213 }214 215 System.out.println("\n通过享元模式,我们只创建了 " + particleSystem.getParticleTypeCount() + 216 " 种粒子类型,但渲染了 " + particleSystem.getActiveParticleCount() + " 个粒子实例。");217 }218}1// 数据库连接接口2public interface DatabaseConnection {3 void executeQuery(String query);4 void close();5 boolean isOpen();6}78// 具体数据库连接9public class ConcreteDatabaseConnection implements DatabaseConnection {10 private final String connectionString;11 private boolean isOpen;12 private final int connectionId;13 14 public ConcreteDatabaseConnection(String connectionString, int connectionId) {15 this.connectionString = connectionString;16 this.connectionId = connectionId;17 this.isOpen = false;18 System.out.println("创建新的数据库连接 #" + connectionId + " 到 " + connectionString);19 }20 21 public void open() {22 if (!isOpen) {23 isOpen = true;24 System.out.println("打开数据库连接 #" + connectionId);25 }26 }27 28 @Override29 public void executeQuery(String query) {30 if (!isOpen) {31 open();32 }33 System.out.println("连接 #" + connectionId + " 执行查询: " + query);34 }35 36 @Override37 public void close() {38 if (isOpen) {39 isOpen = false;40 System.out.println("关闭数据库连接 #" + connectionId);41 }42 }43 44 @Override45 public boolean isOpen() {46 return isOpen;47 }48}4950// 数据库连接池51public class DatabaseConnectionPool {52 private final String connectionString;53 private final List<DatabaseConnection> availableConnections;54 private final List<DatabaseConnection> usedConnections;55 private final int maxConnections;56 private int nextConnectionId = 1;57 58 public DatabaseConnectionPool(String connectionString, int initialPoolSize, int maxConnections) {59 this.connectionString = connectionString;60 this.maxConnections = maxConnections;61 this.availableConnections = new ArrayList<>();62 this.usedConnections = new ArrayList<>();63 64 // 初始化连接池65 for (int i = 0; i < initialPoolSize; i++) {66 availableConnections.add(createConnection());67 }68 }69 70 // 创建新连接71 private DatabaseConnection createConnection() {72 return new ConcreteDatabaseConnection(connectionString, nextConnectionId++);73 }74 75 // 获取连接76 public synchronized DatabaseConnection getConnection() {77 DatabaseConnection connection;78 79 if (availableConnections.isEmpty()) {80 // 如果没有可用连接,并且未达到最大连接数,则创建新连接81 if (usedConnections.size() < maxConnections) {82 connection = createConnection();83 } else {84 // 如果已达到最大连接数,则等待85 System.out.println("达到最大连接数,等待连接释放...");86 try {87 wait(1000); // 等待1秒88 return getConnection(); // 递归调用89 } catch (InterruptedException e) {90 Thread.currentThread().interrupt();91 throw new RuntimeException("获取连接被中断", e);92 }93 }94 } else {95 // 使用池中的可用连接96 connection = availableConnections.remove(availableConnections.size() - 1);97 }98 99 usedConnections.add(connection);100 return connection;101 }102 103 // 释放连接104 public synchronized void releaseConnection(DatabaseConnection connection) {105 if (usedConnections.remove(connection)) {106 availableConnections.add(connection);107 notifyAll(); // 通知等待连接的线程108 System.out.println("连接返回到池中");109 }110 }111 112 // 获取连接池统计信息113 public synchronized String getStatistics() {114 return String.format("连接池统计: 可用连接=%d, 已用连接=%d, 总连接=%d, 最大连接数=%d",115 availableConnections.size(), usedConnections.size(),116 availableConnections.size() + usedConnections.size(), maxConnections);117 }118 119 // 关闭所有连接120 public synchronized void closeAll() {121 for (DatabaseConnection connection : availableConnections) {122 connection.close();123 }124 availableConnections.clear();125 126 for (DatabaseConnection connection : usedConnections) {127 connection.close();128 }129 usedConnections.clear();130 131 System.out.println("所有连接已关闭");132 }133}134135// 客户端代码136public class ConnectionPoolDemo {137 public static void main(String[] args) {138 // 创建连接池,初始大小为3,最大连接数为5139 DatabaseConnectionPool pool = new DatabaseConnectionPool(140 "jdbc:mysql://localhost:3306/mydb", 3, 5);141 142 System.out.println(pool.getStatistics());143 144 // 模拟多个客户端使用连接145 System.out.println("\n--- 客户端1获取连接 ---");146 DatabaseConnection conn1 = pool.getConnection();147 conn1.executeQuery("SELECT * FROM users WHERE id = 1");148 System.out.println(pool.getStatistics());149 150 System.out.println("\n--- 客户端2获取连接 ---");151 DatabaseConnection conn2 = pool.getConnection();152 conn2.executeQuery("SELECT * FROM products WHERE category = 'Electronics'");153 System.out.println(pool.getStatistics());154 155 System.out.println("\n--- 客户端3获取连接 ---");156 DatabaseConnection conn3 = pool.getConnection();157 conn3.executeQuery("SELECT * FROM orders WHERE date > '2023-01-01'");158 System.out.println(pool.getStatistics());159 160 System.out.println("\n--- 客户端1释放连接 ---");161 pool.releaseConnection(conn1);162 System.out.println(pool.getStatistics());163 164 System.out.println("\n--- 客户端4获取连接 ---");165 DatabaseConnection conn4 = pool.getConnection();166 conn4.executeQuery("SELECT * FROM customers WHERE country = 'China'");167 System.out.println(pool.getStatistics());168 169 System.out.println("\n--- 客户端5获取连接 ---");170 DatabaseConnection conn5 = pool.getConnection();171 conn5.executeQuery("INSERT INTO logs (message) VALUES ('System check')");172 System.out.println(pool.getStatistics());173 174 // 释放所有连接175 System.out.println("\n--- 释放所有连接 ---");176 pool.releaseConnection(conn2);177 pool.releaseConnection(conn3);178 pool.releaseConnection(conn4);179 pool.releaseConnection(conn5);180 System.out.println(pool.getStatistics());181 182 // 关闭连接池183 System.out.println("\n--- 关闭连接池 ---");184 pool.closeAll();185 }186}6.5 享元模式与其他模式比较
| 特性 | 享元模式 | 单例模式 | 对象池模式 |
|---|---|---|---|
| 目的 | 共享细粒度对象 | 保证唯一实例 | 重用对象实例 |
| 共享级别 | 多个相似对象 | 单一对象 | 同类型对象集合 |
| 状态处理 | 内外状态分离 | 内部状态 | 重置内部状态 |
| 创建时机 | 首次请求时 | 加载或首次请求 | 池初始化时 |
| 适用场景 | 大量相似对象 | 全局唯一资源 | 创建开销大的对象 |
- 明确区分状态:清晰划分内部状态和外部状态
- 不可变内部状态:享元对象的内部状态应该是不可变的
- 高效键生成:为享元工厂设计高效的对象查找机制
- 懒加载创建:按需创建享元对象,避免预先创建所有可能的对象
- 线程安全:在多线程环境中确保享元工厂的线程安全
7. 代理模式(Proxy)
7.1 模式定义
代理模式为其他对象提供一种代理以控制对这个对象的访问。代理模式创建一个代表另一个对象的替代对象,以控制对原始对象的访问,这种控制可以在客户端和目标对象之间插入其他逻辑。
- 适用场景
- 优点
- 缺点
- 延迟初始化:当目标对象的创建开销很大时,推迟到真正需要时才创建
- 访问控制:控制对原始对象的访问权限
- 附加功能:添加额外操作,如日志记录、性能监控
- 远程代理:为远程对象提供本地代表
- 智能引用:在访问对象时执行额外操作,如引用计数
- 控制访问:可以控制对目标对象的访问
- 开闭原则:不修改目标类的情况下扩展功能
- 延迟加载:按需创建开销大的对象
- 安全性:通过代理限制对敏感对象的访问
- 远程交互:可以隐藏底层网络通信的复杂性
- 性能开销:代理层会带来额外的间接调用
- 复杂性增加:引入额外的类和间接层
- 实现困难:某些代理模式(如远程代理)实现复杂
- 接口同步:代理和实际对象需保持接口一致
- 响应延迟:可能导致操作延迟
7.2 代理模式分类
静态代理
静态代理在编译时就已经确定了代理与真实主题的关系,通过组合实现。
1// 抽象主题2public interface Subject {3 void request();4}56// 真实主题7public class RealSubject implements Subject {8 @Override9 public void request() {10 System.out.println("真实主题处理请求");11 }12}1314// 代理类15public class Proxy implements Subject {16 private RealSubject realSubject;17 18 public Proxy() {19 // 可以延迟初始化20 }21 22 @Override23 public void request() {24 // 前置处理25 System.out.println("代理前置处理");26 27 // 懒加载28 if (realSubject == null) {29 realSubject = new RealSubject();30 }31 32 // 调用真实主题33 realSubject.request();34 35 // 后置处理36 System.out.println("代理后置处理");37 }38}3940// 客户端41public class Client {42 public static void main(String[] args) {43 Subject proxy = new Proxy();44 proxy.request();45 }46}动态代理
动态代理在运行时动态创建代理类,Java中常用的有JDK动态代理和CGLIB。
1import java.lang.reflect.InvocationHandler;2import java.lang.reflect.Method;3import java.lang.reflect.Proxy;45// JDK动态代理处理器6public class DynamicProxyHandler implements InvocationHandler {7 private Object target;8 9 public DynamicProxyHandler(Object target) {10 this.target = target;11 }12 13 @Override14 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {15 // 前置处理16 System.out.println("动态代理前置处理: " + method.getName());17 18 // 调用目标方法19 Object result = method.invoke(target, args);20 21 // 后置处理22 System.out.println("动态代理后置处理: " + method.getName());23 24 return result;25 }26 27 // 创建代理对象28 public static Object createProxy(Object target) {29 return Proxy.newProxyInstance(30 target.getClass().getClassLoader(),31 target.getClass().getInterfaces(),32 new DynamicProxyHandler(target)33 );34 }35}3637// 客户端38public class DynamicProxyDemo {39 public static void main(String[] args) {40 // 创建真实主题41 Subject realSubject = new RealSubject();42 43 // 创建动态代理44 Subject proxy = (Subject) DynamicProxyHandler.createProxy(realSubject);45 46 // 通过代理调用方法47 proxy.request();48 49 // 如果Subject接口有多个方法,都会被代理50 // 例如:proxy.anotherMethod();51 }52}CGLIB代理
CGLIB代理通过生成目标类的子类实现代理,可以代理没有实现接口的类。
1import net.sf.cglib.proxy.Enhancer;2import net.sf.cglib.proxy.MethodInterceptor;3import net.sf.cglib.proxy.MethodProxy;45// CGLIB方法拦截器6public class CglibProxy implements MethodInterceptor {7 8 // 创建代理对象9 public static Object createProxy(Class<?> targetClass) {10 Enhancer enhancer = new Enhancer();11 enhancer.setSuperclass(targetClass);12 enhancer.setCallback(new CglibProxy());13 return enhancer.create();14 }15 16 @Override17 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {18 // 前置处理19 System.out.println("CGLIB代理前置处理: " + method.getName());20 21 // 调用目标方法22 Object result = proxy.invokeSuper(obj, args);23 24 // 后置处理25 System.out.println("CGLIB代理后置处理: " + method.getName());26 27 return result;28 }29}3031// 不需要实现接口的目标类32public class TargetObject {33 public void request() {34 System.out.println("目标对象处理请求");35 }36}3738// 客户端39public class CglibProxyDemo {40 public static void main(String[] args) {41 // 创建代理42 TargetObject proxy = (TargetObject) CglibProxy.createProxy(TargetObject.class);43 44 // 通过代理调用方法45 proxy.request();46 }47}7.3 代理模式的类型
- 虚拟代理
- 保护代理
- 远程代理
- 日志代理
虚拟代理延迟创建开销大的对象,直到真正需要它时才创建。
1// 图片接口2public interface Image {3 void display();4}56// 真实图片 - 代价昂贵的对象7public class RealImage implements Image {8 private String filename;9 10 public RealImage(String filename) {11 this.filename = filename;12 loadFromDisk(); // 开销大的操作13 }14 15 private void loadFromDisk() {16 System.out.println("从磁盘加载图片: " + filename);17 // 模拟加载延迟18 try {19 Thread.sleep(1000);20 } catch (InterruptedException e) {21 e.printStackTrace();22 }23 }24 25 @Override26 public void display() {27 System.out.println("显示图片: " + filename);28 }29}3031// 虚拟代理32public class ProxyImage implements Image {33 private String filename;34 private RealImage realImage;35 36 public ProxyImage(String filename) {37 this.filename = filename;38 }39 40 @Override41 public void display() {42 if (realImage == null) {43 System.out.println("首次访问,初始化RealImage");44 realImage = new RealImage(filename);45 }46 realImage.display();47 }48}4950// 客户端51public class VirtualProxyDemo {52 public static void main(String[] args) {53 // 使用代理创建图片54 System.out.println("创建代理对象...");55 Image image = new ProxyImage("sample.jpg");56 57 // 图片未加载58 System.out.println("代理创建完成,但RealImage尚未加载");59 60 // 首次显示图片时,代理会加载RealImage61 System.out.println("第一次调用display()...");62 image.display();63 64 // 第二次显示图片时,不会重新加载65 System.out.println("\n第二次调用display()...");66 image.display();67 }68}保护代理控制对原始对象的访问,通常用于权限控制。
1// 用户实体2public class User {3 private String id;4 private String name;5 private String role;6 7 public User(String id, String name, String role) {8 this.id = id;9 this.name = name;10 this.role = role;11 }12 13 public String getId() { return id; }14 public String getName() { return name; }15 public String getRole() { return role; }16 17 @Override18 public String toString() {19 return "User{id='" + id + "', name='" + name + "', role='" + role + "'}";20 }21}2223// 用户服务接口24public interface UserService {25 User getUser(String id);26 void updateUser(User user);27 void deleteUser(String id);28}2930// 真实用户服务31public class UserServiceImpl implements UserService {32 private Map<String, User> users = new HashMap<>();33 34 public UserServiceImpl() {35 // 初始化一些用户36 users.put("1", new User("1", "管理员", "admin"));37 users.put("2", new User("2", "普通用户", "user"));38 users.put("3", new User("3", "访客", "guest"));39 }40 41 @Override42 public User getUser(String id) {43 System.out.println("获取用户信息: " + id);44 return users.get(id);45 }46 47 @Override48 public void updateUser(User user) {49 System.out.println("更新用户: " + user);50 users.put(user.getId(), user);51 }52 53 @Override54 public void deleteUser(String id) {55 System.out.println("删除用户: " + id);56 users.remove(id);57 }58}5960// 保护代理61public class UserServiceProxy implements UserService {62 private UserService userService;63 private User currentUser;64 65 public UserServiceProxy(UserService userService, User currentUser) {66 this.userService = userService;67 this.currentUser = currentUser;68 }69 70 @Override71 public User getUser(String id) {72 // 所有用户都可以获取用户信息73 return userService.getUser(id);74 }75 76 @Override77 public void updateUser(User user) {78 // 只有管理员或用户本人可以更新用户信息79 if ("admin".equals(currentUser.getRole()) || currentUser.getId().equals(user.getId())) {80 userService.updateUser(user);81 } else {82 throw new SecurityException("无权限更新用户: " + user.getId());83 }84 }85 86 @Override87 public void deleteUser(String id) {88 // 只有管理员可以删除用户89 if ("admin".equals(currentUser.getRole())) {90 userService.deleteUser(id);91 } else {92 throw new SecurityException("无权限删除用户: " + id);93 }94 }95}9697// 客户端98public class ProtectionProxyDemo {99 public static void main(String[] args) {100 // 创建真实服务101 UserService realService = new UserServiceImpl();102 103 // 以管理员身份创建代理104 User adminUser = new User("1", "管理员", "admin");105 UserService adminProxy = new UserServiceProxy(realService, adminUser);106 107 // 以普通用户身份创建代理108 User normalUser = new User("2", "普通用户", "user");109 UserService userProxy = new UserServiceProxy(realService, normalUser);110 111 // 管理员操作112 System.out.println("=== 管理员操作 ===");113 System.out.println("获取用户: " + adminProxy.getUser("3"));114 adminProxy.updateUser(new User("3", "访客更新", "guest"));115 adminProxy.deleteUser("3");116 117 try {118 // 普通用户操作119 System.out.println("\n=== 普通用户操作 ===");120 System.out.println("获取用户: " + userProxy.getUser("1"));121 122 // 更新自己的信息(允许)123 userProxy.updateUser(new User("2", "普通用户更新", "user"));124 125 // 尝试更新他人信息(不允许)126 userProxy.updateUser(new User("1", "尝试更新管理员", "admin"));127 } catch (SecurityException e) {128 System.out.println("权限错误: " + e.getMessage());129 }130 131 try {132 // 尝试删除用户(不允许)133 userProxy.deleteUser("1");134 } catch (SecurityException e) {135 System.out.println("权限错误: " + e.getMessage());136 }137 }138}远程代理为远程对象提供本地代表,隐藏网络通信的复杂性。
1// 假设这是远程接口2public interface RemoteService {3 String getData(String param);4 void processData(String data);5}67// 远程服务实现(模拟)8public class RemoteServiceImpl implements RemoteService {9 @Override10 public String getData(String param) {11 return "远程数据: " + param;12 }13 14 @Override15 public void processData(String data) {16 System.out.println("远程处理数据: " + data);17 }18}1920// 远程代理21public class RemoteServiceProxy implements RemoteService {22 private String serviceUrl;23 24 public RemoteServiceProxy(String serviceUrl) {25 this.serviceUrl = serviceUrl;26 }27 28 @Override29 public String getData(String param) {30 System.out.println("发送远程请求到: " + serviceUrl + "/getData");31 32 // 模拟网络延迟33 try {34 Thread.sleep(500);35 } catch (InterruptedException e) {36 e.printStackTrace();37 }38 39 // 在实际应用中,这里会执行HTTP请求40 // 这里为了演示,直接创建远程服务的实例41 RemoteService remoteService = new RemoteServiceImpl();42 String result = remoteService.getData(param);43 44 System.out.println("接收远程响应");45 return result;46 }47 48 @Override49 public void processData(String data) {50 System.out.println("发送远程请求到: " + serviceUrl + "/processData");51 52 // 模拟网络延迟53 try {54 Thread.sleep(500);55 } catch (InterruptedException e) {56 e.printStackTrace();57 }58 59 // 在实际应用中,这里会执行HTTP请求60 RemoteService remoteService = new RemoteServiceImpl();61 remoteService.processData(data);62 63 System.out.println("远程调用完成");64 }65}6667// 客户端68public class RemoteProxyDemo {69 public static void main(String[] args) {70 // 创建远程服务代理71 RemoteService service = new RemoteServiceProxy("http://example.com/api");72 73 // 获取远程数据74 String data = service.getData("test");75 System.out.println("获取的数据: " + data);76 77 // 处理数据78 service.processData("处理测试数据");79 }80}日志代理添加日志记录功能,而不修改原始对象。
1// 日志代理使用JDK动态代理实现2public class LoggingProxyHandler implements InvocationHandler {3 private Object target;4 private Logger logger;5 6 public LoggingProxyHandler(Object target) {7 this.target = target;8 this.logger = Logger.getLogger(target.getClass().getName());9 }10 11 @Override12 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {13 // 记录方法调用14 logger.info("调用方法: " + method.getName());15 16 // 记录参数17 if (args != null) {18 for (int i = 0; i < args.length; i++) {19 logger.info("参数 " + (i + 1) + ": " + args[i]);20 }21 }22 23 try {24 // 调用目标方法25 long startTime = System.currentTimeMillis();26 Object result = method.invoke(target, args);27 long endTime = System.currentTimeMillis();28 29 // 记录返回值和执行时间30 logger.info("方法 " + method.getName() + " 返回: " + result);31 logger.info("执行时间: " + (endTime - startTime) + "ms");32 33 return result;34 } catch (Exception e) {35 // 记录异常36 logger.severe("方法 " + method.getName() + " 抛出异常: " + e.getCause());37 throw e.getCause();38 }39 }40 41 // 创建代理42 public static <T> T createProxy(T target, Class<?>... interfaces) {43 return (T) Proxy.newProxyInstance(44 target.getClass().getClassLoader(),45 interfaces,46 new LoggingProxyHandler(target)47 );48 }49}5051// 业务服务52public interface OrderService {53 Order createOrder(String productId, int quantity);54 boolean cancelOrder(String orderId);55}5657// 业务服务实现58public class OrderServiceImpl implements OrderService {59 @Override60 public Order createOrder(String productId, int quantity) {61 // 模拟创建订单62 Order order = new Order("ORD-" + System.currentTimeMillis(), productId, quantity);63 System.out.println("创建订单: " + order);64 return order;65 }66 67 @Override68 public boolean cancelOrder(String orderId) {69 // 模拟取消订单70 System.out.println("取消订单: " + orderId);71 return true;72 }73}7475// 订单实体76public class Order {77 private String orderId;78 private String productId;79 private int quantity;80 81 public Order(String orderId, String productId, int quantity) {82 this.orderId = orderId;83 this.productId = productId;84 this.quantity = quantity;85 }86 87 @Override88 public String toString() {89 return "Order{orderId='" + orderId + "', productId='" + productId + "', quantity=" + quantity + "}";90 }91}9293// 客户端94public class LoggingProxyDemo {95 public static void main(String[] args) {96 // 创建真实服务97 OrderService orderService = new OrderServiceImpl();98 99 // 创建日志代理100 OrderService proxiedService = LoggingProxyHandler.createProxy(orderService, OrderService.class);101 102 // 通过代理调用服务103 Order order = proxiedService.createOrder("PROD-1", 5);104 105 // 取消订单106 boolean cancelled = proxiedService.cancelOrder(order.getOrderId());107 }108}7.4 代理模式与AOP
代理模式是面向切面编程(AOP)的基础,Spring AOP就是通过动态代理实现的。
1// 切面接口2public interface Aspect {3 void before(Method method, Object[] args);4 void after(Method method, Object result);5 void afterThrowing(Method method, Exception e);6}78// 日志切面9public class LoggingAspect implements Aspect {10 @Override11 public void before(Method method, Object[] args) {12 System.out.println("调用前 - 方法: " + method.getName());13 }14 15 @Override16 public void after(Method method, Object result) {17 System.out.println("调用后 - 方法: " + method.getName() + ", 结果: " + result);18 }19 20 @Override21 public void afterThrowing(Method method, Exception e) {22 System.out.println("异常发生 - 方法: " + method.getName() + ", 异常: " + e.getMessage());23 }24}2526// AOP代理处理器27public class AopProxyHandler implements InvocationHandler {28 private Object target;29 private List<Aspect> aspects;30 31 public AopProxyHandler(Object target) {32 this.target = target;33 this.aspects = new ArrayList<>();34 }35 36 public void addAspect(Aspect aspect) {37 aspects.add(aspect);38 }39 40 @Override41 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {42 // 调用所有切面的before方法43 for (Aspect aspect : aspects) {44 aspect.before(method, args);45 }46 47 try {48 // 调用目标方法49 Object result = method.invoke(target, args);50 51 // 调用所有切面的after方法52 for (Aspect aspect : aspects) {53 aspect.after(method, result);54 }55 56 return result;57 } catch (InvocationTargetException e) {58 // 调用所有切面的afterThrowing方法59 for (Aspect aspect : aspects) {60 aspect.afterThrowing(method, (Exception)e.getCause());61 }62 throw e.getCause();63 }64 }65 66 // 创建AOP代理67 public static <T> T createProxy(T target, Class<?>... interfaces) {68 AopProxyHandler handler = new AopProxyHandler(target);69 return (T) Proxy.newProxyInstance(70 target.getClass().getClassLoader(),71 interfaces,72 handler73 );74 }75}7677// 简单AOP框架演示78public class SimpleAopDemo {79 public static void main(String[] args) {80 // 创建真实服务81 OrderService orderService = new OrderServiceImpl();82 83 // 创建AOP代理处理器84 AopProxyHandler handler = new AopProxyHandler(orderService);85 86 // 添加日志切面87 handler.addAspect(new LoggingAspect());88 89 // 创建代理90 OrderService proxiedService = (OrderService) Proxy.newProxyInstance(91 orderService.getClass().getClassLoader(),92 new Class<?>[]{OrderService.class},93 handler94 );95 96 // 通过代理调用服务97 Order order = proxiedService.createOrder("PROD-1", 5);98 99 System.out.println();100 101 // 取消订单102 boolean cancelled = proxiedService.cancelOrder(order.getOrderId());103 }104}7.5 代理模式与其他模式比较
| 特性 | 代理模式 | 装饰器模式 | 适配器模式 | 外观模式 |
|---|---|---|---|---|
| 目的 | 控制访问 | 动态添加职责 | 接口转换 | 简化接口 |
| 实现方式 | 组合或继承 | 组合 | 组合或继承 | 组合 |
| 关系类型 | 一对一 | 一对一 | 一对一 | 一对多 |
| 透明度 | 可透明,可不透明 | 透明 | 不透明 | 不透明 |
| 创建时机 | 编译时或运行时 | 运行时 | 编译时 | 编译时 |
| 功能扩展 | 访问控制 | 功能扩展 | 接口适配 | 接口简化 |
- 选择合适的代理类型:根据需求选择静态代理、JDK动态代理或CGLIB
- 遵循接口设计:确保代理和真实主题实现相同的接口
- 关注点分离:代理负责控制访问,真实主题负责业务逻辑
- 性能考虑:动态代理可能带来性能开销,在性能敏感场景谨慎使用
- 组合使用:可以将不同类型的代理组合使用,如虚拟代理+保护代理
8. 结构型模式对比
8.1 模式选择指南
| 模式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 适配器 | 接口不兼容 | 提高复用性 | 增加复杂度 |
| 桥接 | 多维度变化 | 避免继承爆炸 | 增加抽象层 |
| 组合 | 树形结构 | 统一接口 | 类型检查困难 |
| 装饰器 | 动态扩展 | 灵活扩展 | 产生小对象 |
| 外观 | 简化接口 | 降低耦合 | 可能成为上帝类 |
| 享元 | 大量对象 | 节省内存 | 增加复杂度 |
| 代理 | 访问控制 | 控制访问 | 增加间接层 |
8.2 模式组合使用
1// 组合模式 + 装饰器模式2public abstract class FileSystemItem {3 public abstract void display(int depth);4 public abstract void add(FileSystemItem item);5 public abstract void remove(FileSystemItem item);6}78public class File extends FileSystemItem {9 private String name;10 11 public File(String name) {12 this.name = name;13 }14 15 @Override16 public void display(int depth) {17 StringBuilder prefix = new StringBuilder();18 for (int i = 0; i < depth; i++) {19 prefix.append(" ");20 }21 System.out.println(prefix + "文件: " + name);22 }23 24 @Override25 public void add(FileSystemItem item) {26 throw new UnsupportedOperationException();27 }28 29 @Override30 public void remove(FileSystemItem item) {31 throw new UnsupportedOperationException();32 }33}3435// 装饰器36public abstract class FileSystemDecorator extends FileSystemItem {37 protected FileSystemItem component;38 39 public FileSystemDecorator(FileSystemItem component) {40 this.component = component;41 }42 43 @Override44 public void display(int depth) {45 component.display(depth);46 }47 48 @Override49 public void add(FileSystemItem item) {50 component.add(item);51 }52 53 @Override54 public void remove(FileSystemItem item) {55 component.remove(item);56 }57}5859public class ReadOnlyDecorator extends FileSystemDecorator {60 public ReadOnlyDecorator(FileSystemItem component) {61 super(component);62 }63 64 @Override65 public void add(FileSystemItem item) {66 throw new UnsupportedOperationException("只读文件系统不能添加文件");67 }68 69 @Override70 public void remove(FileSystemItem item) {71 throw new UnsupportedOperationException("只读文件系统不能删除文件");72 }73}- 适配器模式:需要接口兼容时使用
- 桥接模式:需要多维度变化时使用
- 组合模式:需要树形结构时使用
- 装饰器模式:需要动态扩展功能时使用
- 外观模式:需要简化复杂接口时使用
- 享元模式:需要大量对象共享时使用
- 代理模式:需要控制对象访问时使用
通过本章的学习,你应该已经掌握了七种结构型模式的原理、实现方式和应用场景。在实际项目中,要根据具体需求选择合适的结构型模式,并注意模式间的组合使用。
通过本章的学习,你应该已经掌握了七种结构型模式的原理、实现方式和应用场景。在实际项目中,要根据具体需求选择合适的结构型模式,并注意模式间的组合使用。
评论