跳到主要内容

结构型模式详解

结构型模式关注类和对象的组合,通过组合机制来创建更复杂的结构。本章将深入探讨七种结构型模式的原理、实现方式和实际应用。

核心价值

结构型模式 = 灵活组合 + 职责分离 + 接口转换

  • 🧩 灵活组合:将对象组合成更复杂的结构,而无需改变其接口
  • 🔄 接口适配:使不兼容的接口能够协同工作
  • 📦 封装变化:将系统中易变部分与稳定部分隔离
  • 🚪 简化接口:为复杂子系统提供简单统一的访问接口

结构型模式概览

模式核心意图关键特点典型应用
适配器模式接口转换使不兼容接口协同工作第三方库集成、旧系统兼容
桥接模式分离抽象与实现抽象与实现独立变化UI渲染、驱动程序
组合模式部分-整体结构统一处理单个和组合对象文件系统、菜单系统
装饰器模式动态扩展功能运行时添加职责I/O流、UI组件扩展
外观模式简化接口统一访问复杂子系统API网关、库封装
享元模式共享细粒度对象减少内存使用字符渲染、缓存池
代理模式控制对象访问为对象提供替代品远程代理、权限控制

1. 适配器模式(Adapter)

1.1 模式定义

适配器模式将一个类的接口转换成客户期望的另一个接口,使原本不兼容的类可以协同工作。它充当了两个不同接口之间的桥梁,就像现实世界中的电源适配器一样。

  • 接口不兼容:需要集成不兼容的接口时
  • 复用现有类:希望复用现有类,但其接口与系统不匹配
  • 中间层适配:在不同系统之间构建中间层
  • 第三方库集成:集成第三方库或旧系统时

1.2 实现方式

类适配器模式

类适配器模式通过继承来实现,适配器同时继承被适配者并实现目标接口。

类适配器模式
java
1// 目标接口
2public interface Target {
3 void request();
4}
5
6// 被适配的类
7public class Adaptee {
8 public void specificRequest() {
9 System.out.println("被适配类的特殊请求");
10 }
11}
12
13// 类适配器
14public class ClassAdapter extends Adaptee implements Target {
15 @Override
16 public void request() {
17 // 调用被适配类的方法
18 specificRequest();
19 }
20}

类适配器使用了Java的继承机制,所以只能继承一个类,但可以实现多个接口。这种方式的优点是可以覆盖被适配类的方法,缺点是它与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 @Override
10 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}
6
7// 第三方支付库
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}
17
18// 支付适配器
19public class PaymentAdapter implements PaymentProcessor {
20 private ThirdPartyPayment thirdPartyPayment;
21
22 public PaymentAdapter(ThirdPartyPayment thirdPartyPayment) {
23 this.thirdPartyPayment = thirdPartyPayment;
24 }
25
26 @Override
27 public void processPayment(double amount) {
28 thirdPartyPayment.pay(amount);
29 }
30
31 @Override
32 public boolean isPaymentSuccessful() {
33 return thirdPartyPayment.getPaymentStatus();
34 }
35}
36
37// 使用示例
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.5 适配器模式与其他模式的区别

模式主要目的应用情景结构特点
适配器模式接口转换使不兼容的接口协同工作包装一个类,转换其接口
装饰器模式增加功能动态添加职责包装一个类,增加其行为
外观模式简化接口提供子系统的简化接口为多个类提供一个统一接口
代理模式控制访问控制对对象的访问包装一个类,控制对它的访问
适配器模式最佳实践
  1. 对象适配器优先:优先使用对象适配器,它更加灵活且符合"组合优于继承"原则
  2. 职责清晰:适配器只负责接口转换,不应添加额外业务逻辑
  3. 考虑双向适配:必要时实现双向适配,允许客户端使用不同的接口
  4. 适度使用:避免过度使用适配器,可能导致系统难以理解
  5. 异常处理:适配器中要处理可能的异常转换

2. 桥接模式(Bridge)

2.1 模式定义

桥接模式将抽象部分与实现部分分离,使它们都可以独立地变化。它通过组合优于继承的设计原则,解决了多层继承带来的类爆炸问题,并提高了系统的可扩展性。

  • 多维度变化:当一个类有两个或多个独立变化的维度时
  • 避免继承爆炸:需要避免由于多层继承导致的子类数量剧增
  • 运行时切换实现:需要在运行时切换不同实现
  • 跨平台应用:需要实现跨平台应用,如驱动程序、图形渲染等

2.2 桥接模式结构

桥接模式
java
1// 实现者接口
2public interface Implementor {
3 void operationImpl();
4}
5
6// 具体实现者A
7public class ConcreteImplementorA implements Implementor {
8 @Override
9 public void operationImpl() {
10 System.out.println("具体实现者A的操作");
11 }
12}
13
14// 具体实现者B
15public class ConcreteImplementorB implements Implementor {
16 @Override
17 public void operationImpl() {
18 System.out.println("具体实现者B的操作");
19 }
20}
21
22// 抽象类
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}
32
33// 精确抽象类
34public class RefinedAbstraction extends Abstraction {
35 public RefinedAbstraction(Implementor implementor) {
36 super(implementor);
37 }
38
39 @Override
40 public void operation() {
41 System.out.println("精确抽象类的操作");
42 implementor.operationImpl();
43 }
44}

2.3 应用场景

图形渲染系统
java
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}
6
7// 矢量渲染器 - 具体实现
8public class VectorRenderer implements Renderer {
9 @Override
10 public void renderCircle(double x, double y, double radius) {
11 System.out.println("矢量渲染圆形: (" + x + ", " + y + "), 半径: " + radius);
12 }
13
14 @Override
15 public void renderRectangle(double x, double y, double width, double height) {
16 System.out.println("矢量渲染矩形: (" + x + ", " + y + "), 尺寸: " + width + "x" + height);
17 }
18}
19
20// 光栅渲染器 - 具体实现
21public class RasterRenderer implements Renderer {
22 @Override
23 public void renderCircle(double x, double y, double radius) {
24 System.out.println("光栅渲染圆形: (" + x + ", " + y + "), 半径: " + radius);
25 }
26
27 @Override
28 public void renderRectangle(double x, double y, double width, double height) {
29 System.out.println("光栅渲染矩形: (" + x + ", " + y + "), 尺寸: " + width + "x" + height);
30 }
31}
32
33// 形状类 - 抽象部分
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}
44
45// 圆形 - 精确抽象
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 @Override
57 public void draw() {
58 renderer.renderCircle(x, y, radius);
59 }
60
61 @Override
62 public void resize(double scale) {
63 radius *= scale;
64 }
65}
66
67// 矩形 - 精确抽象
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 @Override
80 public void draw() {
81 renderer.renderRectangle(x, y, width, height);
82 }
83
84 @Override
85 public void resize(double scale) {
86 width *= scale;
87 height *= scale;
88 }
89}
90
91// 客户端代码
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. 形状(圆形、矩形)- 抽象部分
  2. 渲染方式(矢量、光栅)- 实现部分

通过桥接模式,我们可以任意组合这两个维度而不需要为每个组合创建一个类。

2.4 桥接模式与其他模式比较

模式桥接模式适配器模式策略模式组合模式
目的分离抽象与实现转换接口封装算法组织对象层次结构
关注点处理多维度变化使不兼容接口兼容提供可替换算法部分-整体关系
设计时机设计前期设计后期/集成期设计中期设计中期
类数量中等中等
复杂度中高

2.5 实现要点

类层次结构

桥接模式创建了两个独立的类层次结构:

  1. 抽象层: Abstraction及其子类
  2. 实现层: Implementor及其实现类

设计步骤

  1. 识别系统中的独立变化维度
  2. 分离抽象部分和实现部分
  3. 创建抽象类及其派生类
  4. 创建实现接口及其实现类
  5. 在抽象类中维护对实现接口的引用
桥接模式最佳实践
  1. 尽早设计:在系统设计初期就应考虑使用桥接模式
  2. 明确分离:确保抽象和实现真正独立且可以单独扩展
  3. 注意粒度:实现接口应该足够抽象,以允许不同的具体实现
  4. 考虑依赖注入:结合依赖注入框架实现桥接模式
  5. 处理异常情况:设计实现类时考虑可能的异常和边界情况

3. 组合模式(Composite)

3.1 模式定义

组合模式将对象组合成树形结构以表示"部分-整体"的层次结构,使得客户端对单个对象和组合对象的使用具有一致性。它允许客户端统一处理单个对象和对象组合,而无需关心其内部复杂结构。

  • 表示部分-整体层次结构:需要表示对象的部分-整体层次结构
  • 统一处理:希望客户端忽略复合对象与单个对象的差异
  • 树形结构:需要处理树形结构,如文件系统、菜单系统等
  • 递归组合:需要递归组合对象以形成更复杂的结构

3.2 组合模式结构

组合模式
java
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}
14
15// 叶子节点
16public class Leaf extends Component {
17 public Leaf(String name) {
18 super(name);
19 }
20
21 @Override
22 public void add(Component component) {
23 throw new UnsupportedOperationException("叶子节点不能添加子节点");
24 }
25
26 @Override
27 public void remove(Component component) {
28 throw new UnsupportedOperationException("叶子节点不能删除子节点");
29 }
30
31 @Override
32 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 @Override
41 public void operation() {
42 System.out.println("叶子节点操作: " + name);
43 }
44}
45
46// 复合节点
47public class Composite extends Component {
48 private List<Component> children = new ArrayList<>();
49
50 public Composite(String name) {
51 super(name);
52 }
53
54 @Override
55 public void add(Component component) {
56 children.add(component);
57 }
58
59 @Override
60 public void remove(Component component) {
61 children.remove(component);
62 }
63
64 @Override
65 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 @Override
78 public void operation() {
79 System.out.println("复合节点操作: " + name);
80 for (Component child : children) {
81 child.operation();
82 }
83 }
84}

3.3 组合模式变体

透明组合模式

透明组合模式在抽象组件中定义所有管理子组件的方法,包括叶子节点和组合节点,这使得客户端可以统一对待所有组件,但叶子节点必须实现一些并不适用的方法。

透明组合模式
java
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}

安全组合模式

安全组合模式只在抽象组件中定义共同操作,而将管理子组件的方法放在组合节点类中,这样可以避免叶子节点实现不必要的方法,但客户端需要区分叶子和组合节点。

安全组合模式
java
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}
14
15public 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 应用场景

文件系统
java
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}
21
22// 文件
23public class File extends FileSystemItem {
24 public File(String name, long size) {
25 super(name, size);
26 }
27
28 @Override
29 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 @Override
38 public long getSize() {
39 return size;
40 }
41
42 @Override
43 public void add(FileSystemItem item) {
44 throw new UnsupportedOperationException("文件不能添加子项");
45 }
46
47 @Override
48 public void remove(FileSystemItem item) {
49 throw new UnsupportedOperationException("文件不能删除子项");
50 }
51
52 @Override
53 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}
69
70// 目录
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 @Override
79 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 @Override
92 public long getSize() {
93 long totalSize = 0;
94 for (FileSystemItem child : children) {
95 totalSize += child.getSize();
96 }
97 return totalSize;
98 }
99
100 @Override
101 public void add(FileSystemItem item) {
102 children.add(item);
103 }
104
105 @Override
106 public void remove(FileSystemItem item) {
107 children.remove(item);
108 }
109
110 @Override
111 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}
127
128// 客户端代码
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}

这个示例模拟了一个简单的文件系统,包含目录和文件。目录可以包含其他目录和文件,而文件是叶子节点。通过组合模式,客户端代码可以统一处理文件和目录。

3.5 组合模式的实现考量

安全性与透明性的权衡

在组合模式实现中,需要权衡安全性和透明性:

  1. 透明性:抽象组件中定义所有操作,使客户端可以统一处理

    • 优点:客户端代码简单,不需要区分叶子和组合节点
    • 缺点:叶子节点必须实现不适用的方法,违反接口隔离原则
  2. 安全性:抽象组件只定义共同操作,管理子组件的方法只在组合节点中定义

    • 优点:叶子节点不需要实现不适用的方法
    • 缺点:客户端需要区分叶子和组合节点,增加类型检查

应对常见挑战

  1. 组件排序:为了支持组件排序,可以在Component中添加getOrder()和setOrder()方法
  2. 访问父组件:在需要向上遍历的场景中,可以在Component中添加对父组件的引用
  3. 缓存优化:为了提高性能,可以缓存组合节点的计算结果,如大小、计数等
带缓存的组合模式
java
1public class CachingComposite extends Component {
2 private List<Component> children = new ArrayList<>();
3 private long cachedSize = -1;
4 private boolean dirty = true;
5
6 @Override
7 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 @Override
20 public void add(Component component) {
21 children.add(component);
22 dirty = true;
23 }
24
25 @Override
26 public void remove(Component component) {
27 children.remove(component);
28 dirty = true;
29 }
30}
组合模式最佳实践
  1. 决定安全性与透明性:根据项目需求,选择透明性或安全性实现
  2. 组件排序:需要排序时,添加排序相关方法和属性
  3. 性能优化:对频繁访问的属性使用缓存策略
  4. 访问控制:考虑是否需要父节点引用,以及访问控制策略
  5. 迭代器实现:提供遍历组合结构的迭代器,简化客户端代码

4. 装饰器模式(Decorator)

4.1 模式定义

装饰器模式动态地给一个对象添加额外的职责,是继承的一种灵活替代方案。它通过组合而非继承来扩展对象的功能,在不修改原有对象的情况下为其添加新的行为。

  • 动态扩展功能:在不改变对象结构的前提下动态地给对象添加职责
  • 避免子类爆炸:当使用继承会导致子类数量剧增时
  • 运行时配置:需要在运行时动态组合多种行为
  • 责任链构建:需要构建多层次的责任链

4.2 装饰器模式结构

装饰器模式结构
java
1// 组件接口
2public interface Component {
3 void operation();
4}
5
6// 具体组件
7public class ConcreteComponent implements Component {
8 @Override
9 public void operation() {
10 System.out.println("具体组件的操作");
11 }
12}
13
14// 抽象装饰器
15public abstract class Decorator implements Component {
16 protected Component component;
17
18 public Decorator(Component component) {
19 this.component = component;
20 }
21
22 @Override
23 public void operation() {
24 component.operation();
25 }
26}
27
28// 具体装饰器A
29public class ConcreteDecoratorA extends Decorator {
30 public ConcreteDecoratorA(Component component) {
31 super(component);
32 }
33
34 @Override
35 public void operation() {
36 super.operation();
37 addedBehavior();
38 }
39
40 private void addedBehavior() {
41 System.out.println("装饰器A添加的行为");
42 }
43}
44
45// 具体装饰器B
46public class ConcreteDecoratorB extends Decorator {
47 public ConcreteDecoratorB(Component component) {
48 super(component);
49 }
50
51 @Override
52 public void operation() {
53 System.out.println("装饰器B的前置处理");
54 super.operation();
55 System.out.println("装饰器B的后置处理");
56 }
57}
58
59// 客户端代码
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 应用场景

Java IO流装饰器
java
1// 基础输入流接口
2public interface DataSource {
3 String read();
4 void write(String data);
5}
6
7// 具体组件:文件数据源
8public class FileDataSource implements DataSource {
9 private String filename;
10
11 public FileDataSource(String filename) {
12 this.filename = filename;
13 }
14
15 @Override
16 public String read() {
17 System.out.println("从文件读取数据: " + filename);
18 return "文件内容";
19 }
20
21 @Override
22 public void write(String data) {
23 System.out.println("向文件写入数据: " + filename);
24 System.out.println("内容: " + data);
25 }
26}
27
28// 抽象装饰器
29public abstract class DataSourceDecorator implements DataSource {
30 protected DataSource wrappee;
31
32 public DataSourceDecorator(DataSource source) {
33 this.wrappee = source;
34 }
35
36 @Override
37 public String read() {
38 return wrappee.read();
39 }
40
41 @Override
42 public void write(String data) {
43 wrappee.write(data);
44 }
45}
46
47// 加密装饰器
48public class EncryptionDecorator extends DataSourceDecorator {
49 public EncryptionDecorator(DataSource source) {
50 super(source);
51 }
52
53 @Override
54 public String read() {
55 String data = super.read();
56 return decrypt(data);
57 }
58
59 @Override
60 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}
84
85// 压缩装饰器
86public class CompressionDecorator extends DataSourceDecorator {
87 public CompressionDecorator(DataSource source) {
88 super(source);
89 }
90
91 @Override
92 public String read() {
93 String data = super.read();
94 return decompress(data);
95 }
96
97 @Override
98 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}
114
115// 缓冲装饰器
116public class BufferedDecorator extends DataSourceDecorator {
117 private StringBuilder buffer = new StringBuilder();
118
119 public BufferedDecorator(DataSource source) {
120 super(source);
121 }
122
123 @Override
124 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 @Override
133 public void write(String data) {
134 System.out.println("使用缓冲写入");
135 buffer.append(data);
136 if (buffer.length() >= 1024) { // 假设缓冲区大小为1024
137 flush();
138 }
139 }
140
141 public void flush() {
142 System.out.println("刷新缓冲区");
143 super.write(buffer.toString());
144 buffer.setLength(0);
145 }
146}
147
148// 客户端代码
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流库是装饰器模式的经典应用。例如,你可以通过组合不同的流装饰器来创建具有多种功能的流:

java
1// 创建一个带缓冲、压缩和加密功能的文件输入流
2InputStream input = new BufferedInputStream(
3 new GZIPInputStream(
4 new CipherInputStream(
5 new FileInputStream("data.gz"),
6 getCipher()
7 )
8 )
9);

4.4 装饰器模式与其他模式对比

特性装饰器模式代理模式组合模式策略模式
目的动态添加功能控制对象访问组织树形结构封装可替换算法
关系类型包装关系代表关系包含关系组合关系
透明性对客户端透明对客户端透明对客户端透明需要显式选择
运行时变化可动态添加职责通常固定可动态组合可动态替换
接口增强可增强接口通常相同接口统一接口完全替换实现

4.5 装饰器模式实现要点

透明性与非透明性装饰器

  1. 透明性装饰器:装饰器完全实现组件接口,对客户端透明

    java
    1// 透明装饰器示例
    2Component component = new ConcreteComponent();
    3component = new ConcreteDecoratorA(component);
    4component = new ConcreteDecoratorB(component);
    5component.operation(); // 客户端仅使用Component接口
  2. 非透明性装饰器:装饰器添加了原组件没有的新方法

    java
    1// 非透明装饰器示例
    2public class LoggingDecorator extends Decorator {
    3 public LoggingDecorator(Component component) {
    4 super(component);
    5 }
    6
    7 @Override
    8 public void operation() {
    9 super.operation();
    10 }
    11
    12 // 新增方法
    13 public void enableLogging() {
    14 System.out.println("启用日志记录");
    15 }
    16}
    17
    18// 客户端需要知道具体装饰器类型
    19LoggingDecorator decorator = new LoggingDecorator(new ConcreteComponent());
    20decorator.operation();
    21decorator.enableLogging(); // 调用装饰器特有方法

装饰顺序影响

装饰器的应用顺序可能会影响最终结果,特别是对于依赖执行顺序的操作。

java
1// 顺序1:先压缩后加密
2DataSource compressedEncrypted = new EncryptionDecorator(new CompressionDecorator(source));
3
4// 顺序2:先加密后压缩
5DataSource encryptedCompressed = new CompressionDecorator(new EncryptionDecorator(source));
装饰器模式最佳实践
  1. 单一职责:每个装饰器只负责一个功能,保持简单明确
  2. 基类稳定:确保组件接口稳定,避免频繁变化
  3. 注意性能:装饰器嵌套层次过多可能影响性能
  4. 顺序敏感性:注意装饰器的应用顺序对结果的影响
  5. 避免过度装饰:过多的装饰层会使系统难以理解和调试

5. 外观模式(Facade)

5.1 模式定义

外观模式为子系统中的一组接口提供一个统一的高层接口,使得子系统更容易使用。它定义了一个高层接口,这个接口使得子系统更加容易访问,降低了客户端与子系统之间的耦合度。

  • 复杂子系统:当需要简化复杂子系统的访问时
  • 分层系统:在分层系统中,使用外观定义每层的入口点
  • 子系统独立:将客户端代码与子系统组件解耦
  • 统一接口:为子系统提供一个简单的统一接口

5.2 外观模式结构

外观模式结构
java
1// 子系统A
2public class SubsystemA {
3 public void operationA() {
4 System.out.println("子系统A的操作");
5 }
6}
7
8// 子系统B
9public class SubsystemB {
10 public void operationB() {
11 System.out.println("子系统B的操作");
12 }
13}
14
15// 子系统C
16public class SubsystemC {
17 public void operationC() {
18 System.out.println("子系统C的操作");
19 }
20}
21
22// 外观类
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}
59
60// 客户端代码
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 外观模式变体

多层外观

在大型系统中,可能需要多个外观类形成层次结构,每个外观负责系统的不同部分。

多层外观
java
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}
16
17// 第二层外观
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}

子系统外观

每个子系统可以有自己的外观类,以简化对该子系统的访问。

子系统外观
java
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 应用场景

家庭影院外观
java
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}
15
16public 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}
37
38public 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}
51
52public 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}
61
62public class Screen {
63 public void down() {
64 System.out.println("屏幕降下");
65 }
66
67 public void up() {
68 System.out.println("屏幕升起");
69 }
70}
71
72public 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}
85
86// 家庭影院外观
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}
138
139// 客户端代码
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}

5.5 外观模式与其他模式的关系

模式外观模式适配器模式中介者模式代理模式
目的简化接口转换接口解耦对象控制访问
适用范围多个对象单个对象多个对象单个对象
交互方式单向调用适配调用协调通信透明代理
侧重点简化复杂性解决不兼容减少耦合增加控制
时机子系统设计后期已有接口不兼容时系统设计初期扩展现有对象
外观模式最佳实践
  1. 接口设计:外观应提供简单、直观的接口,隐藏子系统复杂性
  2. 子系统独立:保持子系统独立,不要让外观成为它们的唯一入口
  3. 多层外观:在大型系统中,考虑使用多层外观分解复杂性
  4. 避免上帝对象:不要让外观承担过多责任,保持单一职责
  5. 组合使用:外观可以与其他模式组合使用,如适配器和策略模式

6. 享元模式(Flyweight)

6.1 模式定义

享元模式运用共享技术来高效地支持大量细粒度对象。它通过共享相同状态的对象实例,减少内存消耗并提高性能,尤其在需要创建大量相似对象的系统中非常有用。

  • 大量相似对象:系统中存在大量相似的对象,造成内存开销
  • 外部状态分离:对象的状态可以分为内部状态和外部状态
  • 对象标识不重要:对象的标识对客户端不重要
  • 常量池:需要缓存和重用对象实例,如字符串常量池

6.2 享元模式结构

享元模式结构
java
1// 享元接口
2public interface Flyweight {
3 void operation(String extrinsicState);
4}
5
6// 具体享元
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 @Override
16 public void operation(String extrinsicState) {
17 System.out.println("具体享元 [" + intrinsicState + "] 执行操作,外部状态: " + extrinsicState);
18 }
19}
20
21// 不共享的具体享元
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 @Override
31 public void operation(String extrinsicState) {
32 System.out.println("不共享的具体享元 [" + allState + "] 执行操作,外部状态: " + extrinsicState);
33 }
34}
35
36// 享元工厂
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}
60
61// 客户端代码
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 内部状态与外部状态

享元模式的核心在于将对象的状态分为内部状态和外部状态:

  1. 内部状态(Intrinsic State)

    • 存储在享元对象内部
    • 可以被多个对象共享
    • 独立于对象的使用场景
    • 通常是不可变的
  2. 外部状态(Extrinsic State)

    • 不存储在享元对象内部
    • 由客户端代码提供
    • 根据使用场景而变化
    • 不可共享
内部与外部状态示例
java
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 @Override
22 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 @Override
32 public int hashCode() {
33 return Objects.hash(character, fontFamily, fontSize);
34 }
35}

6.4 应用场景

文本编辑器享元
java
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}
18
19// 字符享元工厂
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}
35
36// 文本文档类,使用享元模式
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}
74
75// 客户端代码
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}

6.5 享元模式与其他模式比较

特性享元模式单例模式对象池模式
目的共享细粒度对象保证唯一实例重用对象实例
共享级别多个相似对象单一对象同类型对象集合
状态处理内外状态分离内部状态重置内部状态
创建时机首次请求时加载或首次请求池初始化时
适用场景大量相似对象全局唯一资源创建开销大的对象
享元模式最佳实践
  1. 明确区分状态:清晰划分内部状态和外部状态
  2. 不可变内部状态:享元对象的内部状态应该是不可变的
  3. 高效键生成:为享元工厂设计高效的对象查找机制
  4. 懒加载创建:按需创建享元对象,避免预先创建所有可能的对象
  5. 线程安全:在多线程环境中确保享元工厂的线程安全

7. 代理模式(Proxy)

7.1 模式定义

代理模式为其他对象提供一种代理以控制对这个对象的访问。代理模式创建一个代表另一个对象的替代对象,以控制对原始对象的访问,这种控制可以在客户端和目标对象之间插入其他逻辑。

  • 延迟初始化:当目标对象的创建开销很大时,推迟到真正需要时才创建
  • 访问控制:控制对原始对象的访问权限
  • 附加功能:添加额外操作,如日志记录、性能监控
  • 远程代理:为远程对象提供本地代表
  • 智能引用:在访问对象时执行额外操作,如引用计数

7.2 代理模式分类

静态代理

静态代理在编译时就已经确定了代理与真实主题的关系,通过组合实现。

静态代理模式
java
1// 抽象主题
2public interface Subject {
3 void request();
4}
5
6// 真实主题
7public class RealSubject implements Subject {
8 @Override
9 public void request() {
10 System.out.println("真实主题处理请求");
11 }
12}
13
14// 代理类
15public class Proxy implements Subject {
16 private RealSubject realSubject;
17
18 public Proxy() {
19 // 可以延迟初始化
20 }
21
22 @Override
23 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}
39
40// 客户端
41public class Client {
42 public static void main(String[] args) {
43 Subject proxy = new Proxy();
44 proxy.request();
45 }
46}

动态代理

动态代理在运行时动态创建代理类,Java中常用的有JDK动态代理和CGLIB。

JDK动态代理
java
1import java.lang.reflect.InvocationHandler;
2import java.lang.reflect.Method;
3import java.lang.reflect.Proxy;
4
5// JDK动态代理处理器
6public class DynamicProxyHandler implements InvocationHandler {
7 private Object target;
8
9 public DynamicProxyHandler(Object target) {
10 this.target = target;
11 }
12
13 @Override
14 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}
36
37// 客户端
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代理通过生成目标类的子类实现代理,可以代理没有实现接口的类。

CGLIB代理
java
1import net.sf.cglib.proxy.Enhancer;
2import net.sf.cglib.proxy.MethodInterceptor;
3import net.sf.cglib.proxy.MethodProxy;
4
5// 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 @Override
17 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}
30
31// 不需要实现接口的目标类
32public class TargetObject {
33 public void request() {
34 System.out.println("目标对象处理请求");
35 }
36}
37
38// 客户端
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 代理模式的类型

虚拟代理延迟创建开销大的对象,直到真正需要它时才创建。

图片加载虚拟代理
java
1// 图片接口
2public interface Image {
3 void display();
4}
5
6// 真实图片 - 代价昂贵的对象
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 @Override
26 public void display() {
27 System.out.println("显示图片: " + filename);
28 }
29}
30
31// 虚拟代理
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 @Override
41 public void display() {
42 if (realImage == null) {
43 System.out.println("首次访问,初始化RealImage");
44 realImage = new RealImage(filename);
45 }
46 realImage.display();
47 }
48}
49
50// 客户端
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 // 首次显示图片时,代理会加载RealImage
61 System.out.println("第一次调用display()...");
62 image.display();
63
64 // 第二次显示图片时,不会重新加载
65 System.out.println("\n第二次调用display()...");
66 image.display();
67 }
68}

7.4 代理模式与AOP

代理模式是面向切面编程(AOP)的基础,Spring AOP就是通过动态代理实现的。

简化的AOP框架
java
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}
7
8// 日志切面
9public class LoggingAspect implements Aspect {
10 @Override
11 public void before(Method method, Object[] args) {
12 System.out.println("调用前 - 方法: " + method.getName());
13 }
14
15 @Override
16 public void after(Method method, Object result) {
17 System.out.println("调用后 - 方法: " + method.getName() + ", 结果: " + result);
18 }
19
20 @Override
21 public void afterThrowing(Method method, Exception e) {
22 System.out.println("异常发生 - 方法: " + method.getName() + ", 异常: " + e.getMessage());
23 }
24}
25
26// 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 @Override
41 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 handler
73 );
74 }
75}
76
77// 简单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 handler
94 );
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 代理模式与其他模式比较

特性代理模式装饰器模式适配器模式外观模式
目的控制访问动态添加职责接口转换简化接口
实现方式组合或继承组合组合或继承组合
关系类型一对一一对一一对一一对多
透明度可透明,可不透明透明不透明不透明
创建时机编译时或运行时运行时编译时编译时
功能扩展访问控制功能扩展接口适配接口简化
代理模式最佳实践
  1. 选择合适的代理类型:根据需求选择静态代理、JDK动态代理或CGLIB
  2. 遵循接口设计:确保代理和真实主题实现相同的接口
  3. 关注点分离:代理负责控制访问,真实主题负责业务逻辑
  4. 性能考虑:动态代理可能带来性能开销,在性能敏感场景谨慎使用
  5. 组合使用:可以将不同类型的代理组合使用,如虚拟代理+保护代理

8. 结构型模式对比

8.1 模式选择指南

模式适用场景优点缺点
适配器接口不兼容提高复用性增加复杂度
桥接多维度变化避免继承爆炸增加抽象层
组合树形结构统一接口类型检查困难
装饰器动态扩展灵活扩展产生小对象
外观简化接口降低耦合可能成为上帝类
享元大量对象节省内存增加复杂度
代理访问控制控制访问增加间接层

8.2 模式组合使用

模式组合示例
java
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}
7
8public class File extends FileSystemItem {
9 private String name;
10
11 public File(String name) {
12 this.name = name;
13 }
14
15 @Override
16 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 @Override
25 public void add(FileSystemItem item) {
26 throw new UnsupportedOperationException();
27 }
28
29 @Override
30 public void remove(FileSystemItem item) {
31 throw new UnsupportedOperationException();
32 }
33}
34
35// 装饰器
36public abstract class FileSystemDecorator extends FileSystemItem {
37 protected FileSystemItem component;
38
39 public FileSystemDecorator(FileSystemItem component) {
40 this.component = component;
41 }
42
43 @Override
44 public void display(int depth) {
45 component.display(depth);
46 }
47
48 @Override
49 public void add(FileSystemItem item) {
50 component.add(item);
51 }
52
53 @Override
54 public void remove(FileSystemItem item) {
55 component.remove(item);
56 }
57}
58
59public class ReadOnlyDecorator extends FileSystemDecorator {
60 public ReadOnlyDecorator(FileSystemItem component) {
61 super(component);
62 }
63
64 @Override
65 public void add(FileSystemItem item) {
66 throw new UnsupportedOperationException("只读文件系统不能添加文件");
67 }
68
69 @Override
70 public void remove(FileSystemItem item) {
71 throw new UnsupportedOperationException("只读文件系统不能删除文件");
72 }
73}
结构型模式选择原则
  1. 适配器模式:需要接口兼容时使用
  2. 桥接模式:需要多维度变化时使用
  3. 组合模式:需要树形结构时使用
  4. 装饰器模式:需要动态扩展功能时使用
  5. 外观模式:需要简化复杂接口时使用
  6. 享元模式:需要大量对象共享时使用
  7. 代理模式:需要控制对象访问时使用

通过本章的学习,你应该已经掌握了七种结构型模式的原理、实现方式和应用场景。在实际项目中,要根据具体需求选择合适的结构型模式,并注意模式间的组合使用。

通过本章的学习,你应该已经掌握了七种结构型模式的原理、实现方式和应用场景。在实际项目中,要根据具体需求选择合适的结构型模式,并注意模式间的组合使用。

评论