Java源码分析：深入探讨Iterator模式——迭代器模式
引言java.util 包中包含了一系列重要的集合类。本文将从源码分析入手，深入研究集合类的内部结构，以及遍历集合的迭代器模式（Iterator Pattern）的源码实现内幕。我们将先讨论根接口 Collection，然后分析抽象类 AbstractList 及其对应的 Iterator 接口，仔细研究迭代器模式的实现原理。说明：本文讨论的源代码版本基于 JDK 1.4.2。JDK 1.5 及后续版本在 java.util 中引入了泛型（Generics），为了简化问题并聚焦核心原理，本文主要讨论 1.4 版本的代码逻辑，但核心设计思想在后续版本中依然适用。集合类的根接口 CollectionCollection 接口是所有集合类的根类型。它的一个主要接口方法如下：boolean add(Object o)add() 方法用于添加一个新元素。注意该方法返回一个 boolean 值，但返回值不是表示添加成功与否。仔细阅读文档可以看到，Collection 规定：如果一个集合拒绝添加这个元素（无论任何原因），都必须抛出异常。返回值表示的意义是：add() 方法执行后，集合的内容是否改变了（即元素有无数量、位置等变化），具体行为由实现类决定。即：如果方法出错，总会抛出异常；返回值仅仅表示该方法执行后这个 Collection 的内容有无变化。类似的方法还有：boolean addAll(Collection c); boolean remove(Object o); boolean removeAll(Collection c); boolean retainAll(Collection c);Object[] toArray() 方法很简单，把集合转换成数组返回。Object[] toArray(Object[] a) 方法则稍微复杂一些：返回的 Object[] 仍然是把集合的所有元素变成的数组，但是类型和参数 a 的类型是相同的。例如执行：String[] o = (String[])c.toArray(new String[0]);得到的 o 实际类型是 String[]。如果参数 a 的大小装不下集合的所有元素，返回的将是一个新的数组。如果参数 a 的大小能装下集合的所有元素，则返回的还是 a，但 a 的内容用集合的元素来填充。尤其要注意的是，如果 a 的大小比集合元素的个数还多，a 后面的部分全部被置为 null。最后一个最重要的方法是 iterator()，返回一个 Iterator（迭代器），用于遍历集合的所有元素。使用 Iterator 模式遍历集合Iterator 模式是用于遍历集合类的标准访问方法。它可以把访问逻辑从不同类型的集合类中抽象出来，从而避免向客户端暴露集合的内部结构。例如，如果没有使用 Iterator，遍历一个数组的方法是使用索引：for(int i = 0; i < array.size(); i++) { // TODO: get(i) ... }而访问一个链表（LinkedList）又必须使用 while 循环：while((e = e.next()) != null) { // TODO: e.data() ... }以上两种方法客户端都必须事先知道集合的内部结构，访问代码和集合本身是紧耦合，无法将访问逻辑从集合类和客户端代码中分离出来。每一种集合对应一种遍历方法，客户端代码无法复用。更严重的是，如果以后需要把 ArrayList 更换为 LinkedList，则原来的客户端代码必须全部重写。为解决以上问题，Iterator 模式总是用同一种逻辑来遍历集合：for(Iterator it = c.iterator(); it.hasNext(); ) { // ... }奥秘在于客户端自身不维护遍历集合的“指针”，所有的内部状态（如当前元素位置，是否有下一个元素）都由 Iterator 来维护。而这个 Iterator 由集合类通过工厂方法生成，因此，它知道如何遍历整个集合。客户端从不直接和集合类打交道，它总是控制 Iterator，向它发送“向前”、“向后”、“取当前元素”的命令，就可以间接遍历整个集合。首先看看 java.util.Iterator 接口的定义：public interface Iterator { boolean hasNext(); Object next(); void remove(); }依赖前两个方法就能完成遍历，典型的代码如下：for(Iterator it = c.iterator(); it.hasNext(); ) { Object o = it.next(); // 对 o 的操作... }在 JDK 1.5 中，还对上面的代码在语法上作了简化（增强型 for 循环）：// Type 是具体的类型，如 String for(Type t : c) { // 对 t 的操作... }每一种集合类返回的 Iterator 具体类型可能不同，Array 可能返回 ArrayIterator，Set 可能返回 SetIterator，Tree 可能返回 TreeIterator。但是它们都实现了 Iterator 接口，因此，客户端不关心到底是哪种 Iterator，它只需要获得这个 Iterator 接口即可，这就是面向对象的威力。Iterator 源码剖析让我们来看看 AbstractList 如何创建 Iterator。首先 AbstractList 定义了一个内部类（inner class）：private class Itr implements Iterator { ... }而 iterator() 方法的定义是：public Iterator iterator() { return new Itr(); }因此客户端不知道它通过 Iterator it = a.iterator() 所获得的 Iterator 的真正类型。现在我们关心的是这个声明为 private 的 Itr 类是如何实现遍历 AbstractList 的：private class Itr implements Iterator { int cursor = 0; int lastRet = -1; int expectedModCount = modCount; }Itr 类依靠 3 个 int 变量（还有一个隐含的 AbstractList 的引用）来实现遍历：cursor：是下一次 next() 调用时元素的位置，第一次调用 next() 将返回索引为 0 的元素。lastRet：记录上一次游标所在位置，因此它总是比 cursor 少 1。expectedModCount：表示期待的 modCount 值，用来判断在遍历过程中集合是否被修改过。变量 cursor 和集合的元素个数决定 hasNext()：public boolean hasNext() { return cursor != size(); }方法 next() 返回的是索引为 cursor 的元素，然后修改 cursor 和 lastRet 的值：public Object next() { checkForComodification(); try { Object next = get(cursor); lastRet = cursor++; return next; } catch(IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } }AbstractList 包含一个 modCount 变量，它的初始值是 0，当集合每被修改一次时（调用 add，remove 等方法），modCount 加 1。因此，modCount 如果不变，表示集合内容未被修改。Itr 初始化时用 expectedModCount 记录集合的 modCount 变量，此后在必要的地方它会检测 modCount 的值：final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }如果 modCount 与一开始记录在 expectedModCount 中的值不等，说明集合内容被修改过，此时会抛出 ConcurrentModificationException。这个 ConcurrentModificationException 是 RuntimeException，不要在客户端捕获它。如果发生此异常，说明程序代码的编写有问题，应该仔细检查代码而不是在 catch 中忽略它。但是调用 Iterator 自身的 remove() 方法删除当前元素是完全没有问题的，因为在这个方法中会自动同步 expectedModCount 和 modCount 的值：public void remove() { ... AbstractList.this.remove(lastRet); ... // 在调用了集合的 remove() 方法之后重新设置了 expectedModCount expectedModCount = modCount; ... }要确保遍历过程顺利完成，必须保证遍历过程中不更改集合的内容（Iterator 的 remove() 方法除外）。因此，确保遍历可靠的原则是只在一个线程中使用这个集合，或者在多线程中对遍历代码进行同步。示例与总结最后给个完整的示例：Collection c = new ArrayList(); c.add("abc"); c.add("xyz"); for(Iterator it = c.iterator(); it.hasNext(); ) { String s = (String)it.next(); System.out.println(s); }如果你把第一行代码的 ArrayList 换成 LinkedList 或 Vector，剩下的代码不用改动一行就能编译，而且功能不变。这就是针对抽象编程的原则：对具体类的依赖性最小。版本说明：本文基于 JDK 1.4.2 源码分析，未涉及泛型特性。现代 Java 开发中建议使用泛型集合（如 List<String>）及增强型 for 循环，但迭代器底层原理（包括 fail-fast 机制）依然保持一致。
Java 设计模式概述设计模式的重要性在软件开发领域，变化是永恒的主题。从项目启动到后期维护，需求变更、技术演进以及环境变化无处不在。我们面临的关键挑战，是如何以最小的成本和最大的灵活性来适应这些变化。幸运的是，前人在应对各种设计问题时积累了丰富的经验，并形成了被广泛认可的最佳实践，这就是“设计模式”（Design Patterns）。设计模式提供了经过验证的解决方案，能够帮助我们构建更优质、更具扩展性的软件系统。接下来，我们将深入探讨两种经典的设计模式：适配器模式和单例模式。适配器模式（Adapter Pattern）1. 应用场景假设存在一个旧系统，原本使用特定接口与系统其他部分交互。当需要引入一个新的第三方库时，问题出现了：新库采用了完全不同的 API，与旧系统适用的接口不兼容。此时，直接修改旧系统的代码以适应新接口是一种高风险且不推荐的做法，尤其是对于大型复杂的旧系统，可能会引发一系列难以预料的问题。适配器模式提供了一种优雅的解决方案：它允许我们创建一个适配器（新的封装类），使不兼容的类能够协同工作。2. 核心原理适配器模式利用接口，将不兼容的接口进行转换，使其能够被客户端正确解析。通过这种方式，它在不兼容的类之间架起了一座桥梁，实现了不同接口之间的通信，而无需修改原有客户端代码。3. 代码实战旧系统接口与客户端代码旧系统使用 LegacyVideoController 接口来控制视频系统：public interface LegacyVideoController { /** * Begins the playback after startTimeTicks * from the beginning of the video * @param startTimeTicks time in milliseconds */ public void startPlayback(long startTimeTicks); // 其他可能的方法... }客户端使用该控制器的方式如下：public void playBackVideo(long timeToStart, LegacyVideoController controller) { if (controller != null) { controller.startPlayback(timeToStart); } }用户需求变更与新接口随着业务发展，新的视频控制器接口 AdvancedVideoController 被引入：public interface AdvancedVideoController { /** * Places the controller head after time * from the beginning of the track * @param time time defines how much seek is required */ public void seek(Time time); /** * Plays the track */ public void play(); }这导致原有的客户端代码失效，因为新接口与旧接口不兼容。适配器类的实现为解决接口不兼容问题，我们创建一个适配器类 AdvancedVideoControllerAdapter：public class AdvancedVideoControllerAdapter implements LegacyVideoController { private AdvancedVideoController advancedVideoController; public AdvancedVideoControllerAdapter(AdvancedVideoController advancedVideoController) { this.advancedVideoController = advancedVideoController; } @Override public void startPlayback(long startTimeTicks) { // 将 long 类型转换为 Time 类型（假设存在相应转换方法） Time startTime = getTime(startTimeTicks); // 适配操作 advancedVideoController.seek(startTime); advancedVideoController.play(); } }适配器实现了目标接口 LegacyVideoController，这样无需更改客户端代码。适配器类持有需要兼容的接口 AdvancedVideoController 的实例，通过 has-a 关系将客户端请求转发给实际实例。适配器的使用与优势现在，我们可以将新对象封装到适配器中，无需修改客户端代码，因为新对象已通过适配器兼容了旧接口：AdvancedVideoController advancedController = controllerFactory.createController(); // 适配 LegacyVideoController controllerAdapter = new AdvancedVideoControllerAdapter(advancedController); playBackVideo(20, controllerAdapter);适配器不仅可以简单地传递值，还能根据需要支持的接口复杂度提供扩展功能。如果目标接口复杂，需要多个类来实现新功能，适配器也可以封装多个对象。4. 与其他模式的比较模式核心目的接口变化结构关系适配器 (Adapter)使不兼容接口能够协同工作改变接口供客户端使用持有被适配者实例装饰器 (Decorator)动态给对象添加新功能增强接口功能包装对象，接口通常一致外观 (Facade)为复杂子系统提供统一接口简化接口，隐藏复杂性持有子系统多个实例代理 (Proxy)控制对目标对象的访问接口通常与目标一致持有目标对象实例桥梁 (Bridge)分离抽象与实现，使其独立变化抽象与实现分离组合抽象与实现部分装饰模式：主要用于给对象添加新功能，会改变对象的接口行为，通过封装对象来实现功能增强。外观模式：将一个或多个复杂子系统的接口抽象为一个更简单的统一接口，隐藏子系统的复杂性。代理模式：为目标对象提供代理，通常用于控制访问、增强功能或资源管理，代理与目标实现相同接口。桥梁模式：核心是将抽象部分与实现部分分离，使它们可以独立变化，不涉及接口适配。单例模式（Singleton Pattern）1. 概念与应用场景单例模式旨在确保一个类只有一个实例，并提供全局访问点。在许多应用场景中，如应用层缓存、线程池、数据库连接等，只需要一个实例就足以满足需求。若存在多个实例，可能会导致资源浪费、数据不一致等问题，甚至影响系统的稳定性和功能实现。2. 实现方式基本实现框架（Java）public class ApplicationCache { private Map<String, Object> attributeMap; // 静态实例 private static ApplicationCache instance; // 静态访问方法 public static ApplicationCache getInstance() { if (instance == null) { instance = new ApplicationCache(); } return instance; } // 私有构造函数 private ApplicationCache() { attributeMap = createCache(); // 初始化缓存 } }在这个例子中，类中有一个与类类型相同的静态成员，通过静态方法 getInstance() 获取实例。采用了延迟初始化（Lazy Initialization）策略，即直到运行时需要实例时才创建。构造函数被设为私有，防止通过 new 关键字创建多个实例。多线程环境下的考虑 - 双重检查锁定在多线程环境中，上述基本实现可能会出现问题。为确保初始化代码只执行一次，可使用双重检查锁定机制（Double-Checked Locking，适用于 Java 5.0 及以上版本）：public class ApplicationCache { private Map<String, Object> attributeMap; // 使用 volatile 关键字，防止 JVM 乱序写操作 private static volatile ApplicationCache instance; public static ApplicationCache getInstance() { // 第一次检查 if (instance == null) { // 同步类级锁 synchronized (ApplicationCache.class) { // 第二次检查 if (instance == null) { instance = new ApplicationCache(); } } } return instance; } private ApplicationCache() { attributeMap = createCache(); // 初始化缓存 } }通过将 instance 变量声明为 volatile，避免了 JVM 的指令重排序问题。在初始化时进行两次 null 检查，有效防止多个线程创建多个实例。虽然也可以选择同步整个静态方法，但这样会增加不必要的性能开销，因为在初始化完成后，后续访问不再需要同步。另一种实现方式 - 饿汉式（不使用延迟初始化）如果不考虑延迟初始化的好处，还可以采用更简单的实现方式：public class ApplicationCache { private Map<String, Object> attributeMap; // 在声明时初始化 private static ApplicationCache instance = new ApplicationCache(); public static ApplicationCache getInstance() { return instance; } // 私有构造函数 private ApplicationCache() { attributeMap = createCache(); // 初始化缓存 } }在类加载时就初始化变量，调用私有构造函数创建实例，保证只有一个实例存在。这种方式代码更简洁，但失去了延迟初始化的特性，可根据项目实际需求选择合适的实现方式。3. 注意事项反射（Reflection）问题：Java 的反射 API 可以调用私有构造函数，这可能导致创建多个实例。为防止这种情况，可以在构造函数中抛出异常来阻止反射创建额外的实例。序列化（Serialization）问题：序列化和反序列化过程可能会创建两个不同的实例。可以通过重写序列化 API 中的 readResolve() 方法来解决，确保反序列化时返回的是单例实例。4. 设计模式与语言无关性虽然本文以 Java 为例介绍设计模式，但实际上设计模式是与编程语言无关的。它们是解决软件设计中常见问题的通用最佳实践方法。例如，在 JavaScript 中实现单例模式的概念与 Java 相同，但具体实现方式会因语言特性而异：var applicationCache = function() { // 私有变量 var instance; function initCache() { return { proxyUrl: "/bin/getCache.json", cachePurgeTime: 5000, permissions: { read: "everyone", write: "admin" } }; } // 公共接口 return { getInstance: function() { if (!instance) instance = initCache(); return instance; }, purgeCache: function() { instance = null; } }; };此外，像 jQuery 等库也大量使用了设计模式，如 Facade 设计模式，用于隐藏子系统的复杂性，为用户提供更简单易用的接口。总结与建议1. 设计模式的合理运用设计模式是软件开发中的强大工具，但并非所有问题都需要使用设计模式来解决。在实际应用中，应根据具体情况仔细分析，避免过度使用设计模式。过度使用可能会导致代码过于复杂，增加维护成本。2. 学习设计模式的意义学习设计模式有助于深入理解其他类库和框架的设计思想。许多流行的类库和框架，如 jQuery、Spring 等，都广泛应用了各种设计模式。掌握设计模式能够更好地阅读和使用这些类库，提高开发效率和软件质量。希望通过本文的介绍，读者能对设计模式有更深入的理解和认识。如果在学习或使用设计模式过程中有任何疑问或需要进一步了解的内容，欢迎留言交流。说明：文中关于单例模式的双重检查锁定（DCL）实现依赖于 Java 5.0 及以上版本的内存模型（JSR-133），在早期版本中 volatile 语义可能不足以保障线程安全。
Java设计模式——装饰模式与代理模式：深度剖析差异与应用
初始困惑：相似表象下的疑问在设计模式的探索旅程中，开发者经常会遇到一些容易混淆的概念。装饰模式（Decorator Pattern）与代理模式（Proxy Pattern）便是其中典型的一对。乍看之下，二者结构相似，均涉及对原始对象的包装，但实际上它们在设计意图与应用场景上有着本质的区别。本文将深入剖析这两种模式，明确它们的差异，并探讨如何在实际开发中正确运用。表面功能的混淆从结构上看，这两种模式都允许在原始对象的方法调用前后添加额外操作。它们都能在不修改原始对象代码的前提下，为其增加新的功能或控制逻辑。这种“非侵入式”的增强能力，是导致两者容易被混淆的主要原因。然而，若深入探究其背后的设计思想，便会发现它们如同两座看似相近的山峰，实则拥有不同的地貌与生态。本质区别：核心设计理念大揭秘装饰模式：动态扩展功能的利器装饰模式的核心关注点在于为对象动态地添加新的功能。它类似于一个灵活的“工具箱”，开发者可以根据需要随时组合不同的装饰者来增强对象的能力。例如，假设有一个基础手机对象，通过装饰模式，可以为其添加手机壳（保护功能）、耳机（音频增强功能）等配件。每添加一个配件，对象的功能便变得更加丰富，且这些配件可以自由组合，具有极高的灵活性。下面是一个简单的 Java 代码示例，展示了装饰模式的实现：// 定义一个接口 interface Shape { void draw(); } // 具体的被装饰对象：圆形 class Circle implements Shape { @Override public void draw() { System.out.println("绘制一个圆形"); } } // 抽象装饰者类 abstract class ShapeDecorator implements Shape { protected Shape decoratedShape; public ShapeDecorator(Shape decoratedShape) { this.decoratedShape = decoratedShape; } @Override public void draw() { decoratedShape.draw(); } } // 具体装饰者类：给形状添加颜色 class ColoredShapeDecorator extends ShapeDecorator { private String color; public ColoredShapeDecorator(Shape decoratedShape, String color) { super(decoratedShape); this.color = color; } @Override public void draw() { super.draw(); System.out.println("用 " + color + " 颜色填充形状"); } }在客户端代码中，我们可以这样使用装饰模式：public class Client { public static void main(String[] args) { Shape circle = new Circle(); // 动态添加颜色装饰 Shape coloredCircle = new ColoredShapeDecorator(circle, "红色"); coloredCircle.draw(); } }代理模式：访问控制与资源管理的守护者代理模式则专注于控制对对象的访问以及管理对象的资源。它类似于一个严格的“管理员”，决定了谁能够访问对象，以及在什么情况下可以访问。代理类可以对客户端隐藏对象的内部细节，在对象周围设置一道安全屏障，只有经过授权的操作才能通过代理类传递到真实对象。例如，在网络访问中，代理服务器可以控制用户对特定网站的访问权限；在对象加载时，代理类可以先进行初始化操作（如延迟加载），然后再将请求转发给真实对象。以下是一个简单的代理模式 Java 代码示例（静态代理）：// 定义一个接口 interface Image { void display(); } // 真实的图像类 class RealImage implements Image { private String fileName; public RealImage(String fileName) { this.fileName = fileName; loadFromDisk(fileName); } private void loadFromDisk(String fileName) { System.out.println("从磁盘加载图像 " + fileName); } @Override public void display() { System.out.println("显示图像 " + fileName); } } // 代理类 class ProxyImage implements Image { private RealImage realImage; private String fileName; public ProxyImage(String fileName) { this.fileName = fileName; } @Override public void display() { // 延迟加载：只有在需要显示时才创建真实对象 if (realImage == null) { realImage = new RealImage(fileName); } realImage.display(); } }在客户端使用代理模式的代码如下（注：此为独立示例，类名在实际项目中需区分）：public class Client { public static void main(String[] args) { Image image = new ProxyImage("test.jpg"); image.display(); } }关键差异对比为了更清晰地展示装饰模式和代理模式的区别，以下是两者的核心维度对比：对比维度装饰模式代理模式设计目的动态地为对象添加功能，增强对象的行为。控制对对象的访问，管理对象的资源，隐藏对象的内部细节。关系确定时机通常在运行时确定装饰者与被装饰者的关系，可以递归地构造装饰链。通常在编译时确定代理和真实对象之间的关系（指静态代理）。构造方式与参数传递将原始对象作为参数传递给装饰者的构造器，在装饰者内部调用原始对象的方法并添加额外行为。代理类在构造函数中创建或持有真实对象的实例，在代理方法中调用真实对象的方法并添加控制逻辑。主要应用场景图形界面组件增强、动态配置对象行为、流处理（如 Java IO）。远程代理、安全代理、延迟加载（虚拟代理）、缓存代理。应用场景：实战中的模式选择装饰模式的应用场景个性化定制功能增强在电商系统中，可以使用装饰模式为商品添加各种促销活动，如打折、满减、赠品等。每个促销活动都可以看作是一个装饰者，通过组合不同的促销活动装饰者，可以为商品提供丰富多样的个性化定价策略，满足不同用户的需求。功能模块扩展在文本编辑器中，可以使用装饰模式为文本编辑功能添加语法检查、自动保存、格式转换等装饰。用户可以根据需求选择启用或禁用这些功能，使编辑器更加灵活和强大。典型的例子是 Java IO 流设计，如 new BufferedReader(new FileReader("file.txt"))。代理模式的应用场景性能优化与资源管理在大型系统中，对于耗时的操作（如数据库查询或复杂计算），可以使用代理模式实现缓存代理。代理类在第一次执行操作时将结果缓存，下次相同请求到来时直接返回缓存结果，从而提高系统性能，减少资源消耗。安全与权限控制在企业级应用中，对于敏感数据的访问（如用户财务信息或公司机密文件），可以使用代理模式创建安全代理。代理类在访问真实对象之前，先进行身份验证和权限检查，只有合法用户且具有足够权限时，才允许访问真实对象，确保系统的安全性。总结与展望通过对装饰模式和代理模式的深入剖析，我们可以清晰地看到它们在设计理念、实现方式和应用场景上的显著差异。理解这些差异，如同掌握了一把开启高效编程之门的钥匙，能够帮助我们在面对不同的软件开发需求时，准确地选择合适的设计模式，打造出更加灵活、可靠、安全的软件系统。在未来的编程探索中，还会遇到更多精彩的设计模式，它们各自都有着独特的魅力和应用价值。保持对技术的热情和好奇心，不断学习和实践，用设计模式的智慧点亮编程之路，方能创造出更加优秀的软件作品。说明：本文代码示例基于标准 Java 语法，适用于 Java 8 及以上版本。设计模式核心思想不随版本变化，但具体实现可结合现代语言特性（如 Lambda 表达式、动态代理等）进行优化。
一、设计模式的分类与设计原则设计模式总体分为三大类：创建型模式（5 种）、结构型模式（7 种）和行为模式（11 种）。在深入了解各种设计模式之前，先明确一下设计所遵循的原则，主要包括：单一职责原则：一个类应该只有一个引起它变化的原因。开放封闭原则（OCP）：功能开放，代码闭合。里氏替换原则：子类型能够替换掉父类型，实现松耦合。接口隔离原则（ISP）：不能出现胖接口，应该打散 DAO，多定义子接口。这些原则为设计模式的合理运用提供了指导方针，确保软件系统具备良好的可维护性、扩展性和灵活性。二、创建型模式1. 单例模式（Singleton）特征描述：构造函数必须声明为私有，防止外部直接实例化该类。类中声明静态常量，用于保存类的惟一实例。为客户提供一个静态的方法，该方法能够返回惟一实例。应用场景：当类只能有一个实例且客户可从一个众所周知的访问点访问它时。例如在一些全局配置类中，整个系统只需要一个配置实例来保证配置的一致性。当这个惟一实例应通过子类化可扩展，且客户无需更改代码就能使用扩展实例时。如在数据库连接池的实现中，单例模式可以确保在整个应用中只有一个连接池实例，同时可以根据不同的数据库类型进行子类扩展。伪单例模式：线程单例：保证在一个线程中只有一个实例。在 Java 中可通过 ThreadLocal 对象实现。在 Hibernate 中，Session 的使用就运用了线程单例，确保每个线程都有自己独立的 Session 实例，避免线程安全问题。在自定义应用中，对于那些没有实现线程安全但又必须保证线程安全的对象，可设置为线程单例。通过线程同步实现线程安全，这样做的好处是可以提高程序在多线程环境下的稳定性和可靠性。类加载器单例：保证在一个类加载器中只有一个类的实例。在 BeanUtils 中，与 Utils 相对应的 Bean 采用了类加载器单例。在自定义应用中，可将需要在应用间相互隔离但又必须使用单例的对象设置成类加载器单例，常用于隔离 WEB 应用程序之间的单例。其具体作法是使用一个 Map，键为类加载器引用，值存储对象。若能通过类加载器对象得到实例则不再创建，否则创建并存储。相对特定上下文单例：其他相对特定上下文的伪单例模式均可采用类似方法，确保在特定的上下文环境中只有一个实例存在，满足不同场景下对单例的需求。2. 原型模式（Prototype）特征描述：实现原型模式的类必须由统一的接口或类继承而来，并且实现接口或类中定义的 clone 方法。在 clone 方法中实现对象克隆，从而创建新的对象。应用场景：当要实例化的类在运行时刻指定时，例如通过动态装载，可利用原型模式快速创建对象。为避免创建与产品类层次平行的工厂类层次时，原型模式提供了一种简洁的对象创建方式。当一个类的实例只有几种不同状态组合中的一种时，建立相应数目的原型并克隆它们比每次用合适状态手工实例化该类更方便。比如在游戏开发中，不同状态的游戏角色可以通过原型模式快速创建。3. 工厂方法模式（Factory Method）特征描述：顶级类定义创建对象的抽象方法，将对象的创建延迟到子类。具体子类实现该方法，返回具体产品。可将工厂方法参数化，以满足不同的创建需求。应用场景：当一个类不知道它所必须创建的对象的类时，可使用工厂方法模式，让子类来决定创建何种对象。当一个类希望由它的子类来指定所创建的对象时。例如在一个图形绘制系统中，父类定义了绘制图形的方法，但具体绘制哪种图形（如圆形、矩形等）由子类决定。当类将创建对象的职责委托给多个帮助子类中的某一个，且希望将哪一个帮助子类是代理者这一信息局部化时，工厂方法模式可以很好地实现这种职责委托。典型案例：Collection 的 iterator 方法返回 Iterator 可认为是工厂方法的一种典型应用，它为遍历集合提供了统一的方式，不同类型的集合可以返回相应类型的迭代器。Hibernate 中，SessionFactory.openSession 是工厂方法模式的体现，根据不同的配置和需求创建合适的 Session 实例。Spring 中，BeanFactory.getBean 和 FactoryBean.getObject 也是工厂方法模式的应用，用于获取不同类型的 Bean 实例。4. 抽象工厂模式（Abstract Factory）特征描述：定义抽象工厂，用于声明创建一系列相关或相互依赖对象的接口。定义抽象产品系列，确定产品的类型和结构。一个具体工厂对应一个具体产品系列，通过实现抽象工厂的方法来创建所需的产品对象。可以使用工厂方法模式或原型模式实现（也可参数化工厂方法），并且通常工厂类应为单例模式，以保证整个系统中只有一个工厂实例来创建产品。存在的问题是无法在已有产品系中增加新的产品，这是其局限性所在。应用场景：当一个系统要独立于它的产品的创建、组合和表示时，抽象工厂模式可以将产品的创建和使用分离，提高系统的灵活性和可维护性。当一个系统要由多个产品系列中的一个来配置时，通过抽象工厂模式可以方便地切换不同的产品系列，满足不同的配置需求。当强调一系列相关的产品对象的设计以便进行联合使用时，抽象工厂模式能够确保所创建的产品对象之间具有良好的兼容性和一致性。当提供一个产品类库，只想显示它们的接口而不是实现时，抽象工厂模式可以隐藏产品的具体实现细节，只提供统一的接口供用户使用。实现方式：工厂模式实现：为每一种产品声明一个工厂方法，在具体工厂类中实现这些方法来创建相应的产品对象。必须为不同的产品系列定义相应的工厂，以确保能够创建出完整的产品系列。原型模式实现：使产品系列中的每一种产品都实现原型模式，即每种产品都可以通过原型复制来创建新的对象。只要为工厂提供某类型产品系的原型，工厂就可以生产出相应产品系的产品，无需为每个产品系都定义相应工厂，简化了工厂的实现。典型案例：抽象工厂模式在 Java 世界中非常常见，如 JMS 中的抽象工厂模式用于创建消息发送和接收相关的对象，JNDI 中的抽象工厂模式用于获取命名服务相关的对象，XML 解析中的抽象工厂模式用于创建解析 XML 文档所需的各种对象。抽象工厂与工厂方法的区别：抽象工厂与工厂方法的联系大于区别，抽象工厂一般是通过工厂方法来实现的。抽象工厂模式侧重于创建一系列相关的产品对象，而工厂方法模式则侧重于让子类决定创建何种具体的产品对象。5. Builder 模式（生成器模式）特征描述：将一个复杂对象的构建与它的表示分离，使得同样的构建过程可以创建不同的表示。通过逐步构建对象的各个部分，最后得到完整的复杂对象。典型案例：XML 解析中 DocumentBuilder 用于构建 XML 文档对象，它将构建 XML 文档的过程和最终的文档表示分离开来，用户可以根据自己的需求构建不同结构的 XML 文档。BeanUtils 中的 ConverterUtils 与 Converter 用于数据类型的转换，通过构建不同的转换规则来实现不同类型数据之间的转换。Spring MVC 中的 ViewResolver 可以看成是 Builder 模式的一种应用，它根据不同的视图名称和配置构建相应的视图对象，用于展示给用户。三、结构型模式1. 适配器模式（Adapter）特征描述：通常由三个主要对象组成 Target、Adaptee 和 Adapter，一般有对象模式和类模式两种。Target 需要调用 Adaptee 中的方法，但 Target 只能调用 Adapter 类型中定义的接口，Adapter 起到了将 Adaptee 的接口转换为 Target 所期望接口的作用。应用场景：当想使用一个已经存在的类，而其接口不符合需求时，适配器模式可以将不兼容的接口转换为可用的接口，例如在将旧系统中的接口适配到新系统中时。当想创建一个可复用的类，使其能与其他不相关或不可预见的类协同工作时，适配器模式可以解决接口不兼容的问题，提高类的复用性。（仅适用于对象 Adapter）当想使用一些已经存在的子类，但不可能对每一个都进行子类化以匹配它们的接口时，对象适配器可以适配其父类接口，方便地将这些子类集成到系统中。2. 桥模式（Bridge）特征描述：将抽象和接口分离开，使抽象部分与它的实现部分分离，两者都可以独立地变化。选择主系类，主系类中包含次系类的顶层类，在主系类的其他方法中调用次系类的方法作为实现。应用场景：当不希望在抽象和它的实现部分之间有固定的绑定关系时，桥模式可以使两者解耦，方便在运行时切换不同的实现。当类的抽象以及它的实现都可以通过生成子类的方法加以扩充时，桥模式提供了一种灵活的架构，便于扩展抽象和实现部分。对抽象的实现部分的修改应对客户不产生影响，即客户的代码不必重新编译，这提高了系统的可维护性。（C++）当想对客户完全隐藏抽象的实现部分时，桥模式可以将实现细节封装在次系类中，只暴露抽象部分给客户。当有许多类要生成，且类层次结构表明必须将一个对象分解成两个部分时（如 Rumbaugh 称的“嵌套的普化”），桥模式可以清晰地划分抽象和实现部分。当想在多个对象间共享实现（可能使用引用计数），但同时要求客户并不知道这一点时，桥模式可以实现这种隐藏共享实现的需求。典型应用：JDBC 中 Connection 与 Driver 的关系是桥模式的应用，Connection 抽象了数据库连接的概念，而 Driver 则是具体的数据库驱动实现，两者通过桥模式解耦，使得可以方便地切换不同的数据库驱动。Hibernate 中 Query 与 Dialect 的关系也是桥模式的体现，Query 提供了查询的抽象，Dialect 则负责与具体数据库方言相关的实现，提高了 Hibernate 在不同数据库上的兼容性和灵活性。3. 组合模式（Composite）特征描述：将对象组合成树形结构以表示“部分 - 整体”的层次结构，用户对单个对象和组合对象的使用具有一致性。需要组件和容器两类成员，实现的关键是容器本身应该是组件的子类。应用场景：当想表示对象的部分 - 整体层次结构时，例如在文件系统中，文件和文件夹可以组成树形结构，文件夹可以包含文件和其他文件夹，组合模式可以很好地描述这种结构。当希望用户忽略组合对象与单个对象的不同，用户将统一地使用组合结构中的所有对象时，组合模式提供了一种简单一致的操作方式，无论是单个对象还是组合对象，用户都可以使用相同的方法进行操作。安全式和透明式：透明式：要求在组件和容器中都定义操作子组件的方法，在这种情况下，叶子结点因为不含有子组件，所以有可能会出现问题，例如在叶子节点调用操作子组件的方法时可能会出现空指针异常等情况。安全式：则只在容器中定义操作子组件的方法，避免了叶子节点出现不必要的问题，但在使用时需要用户清楚对象的类型是组件还是容器。典型应用：AWT 中的组件和容器，如 Container 和 Component，使用了组合模式来构建用户界面的层次结构，方便管理和操作界面元素。JUnit 中的 TestSuite 和 TestCase 也运用了组合模式，TestSuite 可以包含多个 TestCase，用户可以像对待单个 TestCase 一样对待 TestSuite，统一进行测试执行等操作。4. 装饰模式（Decorator）特征描述：包含组件和装饰类两个构件，关键点在于装饰类一要从组件继承，二是装饰类要由组件组成。通过装饰类可以动态地给一个对象添加一些额外的职责。应用场景：在不影响其他对象的情况下，以动态、透明的方式给单个对象添加职责，例如在一个图形绘制系统中，可以动态地给图形对象添加边框、阴影等装饰效果。处理那些可以撤消的职责，比如在一个权限管理系统中，可以临时给用户添加某些权限，之后又可以方便地撤消这些权限。当不能采用生成子类的方法进行扩充时，一种情况是可能有大量独立的扩展，为支持每一种组合将产生大量的子类，使得子类数目呈爆炸性增长；另一种情况可能是因为类定义被隐藏，或类定义不能用于生成子类，此时装饰模式是一种很好的替代方案。典型应用：IO 中的很多类都用到了 Decorator，如 BufferedReader 等，BufferedReader 对 Reader 进行了装饰，增加了缓冲功能，提高了读取效率。5. 外观模式（Facade）特征描述：关键是为系统提供一个统一对外的接口，将子系统中的一组接口进行整合，形成一个简单易用的高层接口。应用场景：当要为一个复杂子系统提供一个简单接口时，外观模式可以隐藏子系统的复杂性，使外部用户能够更方便地使用子系统的功能，例如在一个电商系统中，订单处理、库存管理、支付等多个子系统可以通过外观模式提供一个统一的订单处理接口给前端应用。当客户程序与抽象类的实现部分之间存在着很大的依赖性时，外观模式可以降低这种依赖性，使客户程序只依赖于外观接口，而不直接依赖于子系统的具体实现，提高系统的可维护性。管理和控制子系统的访问。例如在企业级应用中，不同的业务模块可能有各自复杂的内部结构，通过外观模式为每个业务模块定义一个统一的入口，便于系统的整体架构设计和维护。典型应用：Hibernate 中的 Session 就是外观模式的体现，它为开发者提供了一个简单统一的接口来操作数据库，隐藏了底层复杂的数据库连接、事务管理、对象持久化等操作。开发者只需通过 Session 接口进行数据的增删改查等操作，而无需关心其背后复杂的实现细节。6. 代理模式（Proxy）应用场景：远程代理（Remote Proxy）：用于在本地代表远程对象，使得客户端可以像调用本地对象一样调用远程对象，隐藏了远程对象的分布式特性，例如在分布式系统中，客户端通过远程代理访问远程服务器上的对象。虚代理（Virtual Proxy）：根据需要创建开销很大的对象。当对象的创建成本较高（如创建一个大型数据库连接对象或复杂的图形对象）时，虚代理可以先返回一个占位符对象，等到真正需要使用对象时才创建实际对象，提高系统性能。保护代理（Protection Proxy）：控制对原始对象的访问，例如在权限管理系统中，保护代理可以根据用户的权限决定是否允许对某个对象进行访问或操作，增强系统的安全性。智能指引（Smart Reference）：取代了简单的指针，在访问对象时执行一些附加操作。典型用途包括对指向实际对象的引用计数，当该对象没有引用时自动释放它（也称为 SmartPointers）；当第一次引用一个持久对象时将它装入内存；在访问一个实际对象前检查是否已经锁定了它，以确保其他对象不能改变它。典型应用：EJB（Enterprise JavaBeans）中使用了代理模式，容器为 EJB 组件创建代理对象，负责管理 EJB 的生命周期、事务、安全等方面，客户端通过代理对象与 EJB 组件进行交互。Hibernate 延迟加载也是代理模式的应用，当加载一个包含关联对象的实体时，Hibernate 不会立即加载关联对象，而是返回一个代理对象，只有当真正访问关联对象时才从数据库中加载实际数据，提高了数据查询的效率，减少了不必要的数据加载。7. 享元模式（Flyweight，也称蝇头模式）特征描述：关键是不能直接创建对象，应该先到享元工厂中查找是否存在具有相同状态的对象，如果存在就不应该再创建了，而是直接使用已有的对象，通过共享技术有效地支持大量细粒度的对象，通常共享一个小的模块。应用场景：当一个应用程序使用了大量的对象，且完全由于使用大量的对象造成很大的存储开销时，享元模式可以通过共享对象来减少内存占用。例如在一个文字处理软件中，可能有大量的字符对象，如果每个字符都单独创建对象会占用大量内存，而使用享元模式可以共享相同字符的对象。当对象的大多数状态都可变为外部状态，且如果删除对象的外部状态，可以用相对较少的共享对象取代很多组对象时，享元模式可以优化对象的存储和使用。例如在图形绘制中，对于颜色等可以作为外部状态的属性，通过共享形状对象来减少对象数量。当应用程序不依赖于对象标识时，由于 Flyweight 对象可以被共享，对于概念上明显有别的对象，标识测试将返回真值，这使得享元模式可以在不影响程序逻辑的前提下共享对象。例如在一个游戏中，多个相同类型的敌人对象可能只需要共享一些基本的属性和行为，而它们在游戏中的位置等外部状态可以不同。四、行为模式1. 职责链模式（Chain of Responsibility）特征描述：使多个对象都有机会处理请求，将这些对象连成一条链，并沿着这条链传递请求，直到有一个对象处理它为止。这种模式的最大好处是回避了 switch case 的使用，使得代码结构更加灵活和可扩展。应用场景：当有多个的对象可以处理一个请求，且哪个对象处理该请求在运行时刻自动确定时，职责链模式可以动态地选择合适的处理对象。例如在一个请假审批系统中，不同级别的领导可以组成一个职责链，请假请求根据请假天数等条件依次传递给不同级别的领导进行审批。当想在不明确指定接收者的情况下，向多个对象中的一个提交一个请求时，职责链模式可以自动找到合适的处理对象，无需提前知道具体的接收者。当可处理一个请求的对象集合应被动态指定时，职责链模式可以方便地添加或移除处理对象，适应系统的变化。例如在一个事件处理系统中，可以根据不同的事件类型动态地添加或改变处理事件的对象链。典型应用：Servlet 中的 Filter 就是职责链模式的应用，多个 Filter 可以组成一个链，对请求进行预处理和后处理，如权限验证、日志记录等操作。Struts2 中的拦截器也是职责链模式的体现，拦截器链可以对请求进行一系列的处理，如参数校验、国际化处理等。2. 命令模式（Command）特征描述：将一个请求封装为一个对象，从而可用不同的请求对客户进行参数化；对请求排队或记录请求日志，以及支持可撤消的操作。命令对象内部包含真正的执行者，命令可以单独处理，也可以拿到任何其他的地方再次运行，这保证了回退的可能。应用场景：当抽象出待执行的动作以参数化某对象时，Command 模式是回调机制的一个面向对象的替代品。例如在一个图形编辑软件中，各种图形操作（如绘制直线、圆形等）可以封装成命令对象，方便进行参数化和管理。当在不同的时刻指定、排列和执行请求，可将负责该请求的命令对象传送给另一个不同的进程并在那儿实现该请求时，命令模式可以方便地实现分布式系统中的任务调度和执行。当需要支持取消操作时，命令模式可以记录操作的历史，方便进行回退操作。例如在文档编辑软件中，撤销和重做功能可以通过命令模式实现。当需要支持修改日志，这样当系统崩溃时，这些修改可以被重做一遍时，命令模式可以记录每个命令的执行情况，用于系统恢复。当用构建在原语操作上的高层操作构造一个系统时，命令模式可以将复杂的操作分解为多个简单的命令对象，便于系统的构建和维护。典型应用：JUnit 中的 TestCase 可以看作是命令模式的应用，每个测试方法可以视为一个命令，JUnit 框架负责执行这些命令并记录测试结果。3. 解释器模式（Interpreter）特征描述：给定一个语言，定义它的文法的一种表示，并定义一个解释器，这个解释器使用该表示来解释语言中的句子。当有一个语言需要解释执行，并且可将该语言中的句子表示为一个抽象语法树时，可使用解释器模式。应用场景：当该文法简单时，解释器模式效果较好。对于复杂的文法，文法的类层次会变得庞大而无法管理，此时语法分析程序生成器等工具是更好的选择，它们无需构建抽象语法树即可解释表达式，能节省空间和时间。但即使在这种情况下，转换器仍可用解释器模式实现，该模式仍是有用的。例如在一些简单的配置文件解析、规则引擎等场景中，如果文法不太复杂，解释器模式可以方便地实现对特定语言的解释执行。4. 迭代器模式（Iterator）特征描述：提供一种方法顺序访问一个聚合对象中各个元素，而又不需暴露该对象的内部表示。一般会在迭代器中包含集合的引用，在迭代方法中通过访问集合的相应方法实现对集合的迭代。应用场景：当访问一个聚合对象的内容而无需暴露它的内部表示时，迭代器模式可以提供一种安全的访问方式。例如在一个列表对象中，用户可以通过迭代器遍历列表元素，而无需了解列表的内部存储结构。当支持对聚合对象的多种遍历时，迭代器模式可以定义不同类型的迭代器，如正向迭代、反向迭代等，满足不同的遍历需求。当为遍历不同的聚合结构提供一个统一的接口（即支持多态迭代）时，迭代器模式使得代码更加通用和可复用。例如，无论是数组、链表还是其他集合类型，都可以使用相同的迭代器接口进行遍历。5. 中介者模式（Mediator）特征描述：用一个中介对象来封装一系列的对象交互，中介者使各个对象不需要显式地相互引用，从而使其耦合松散，而且可以独立地改变它们之间的交互。同事间互不相知，由中介者完成同事间交互，同事只知道中介者，即具有一个中介者属性。应用场景：当一组对象以定义良好但是复杂的方式进行通信，产生的相互依赖关系结构混乱且难以理解时，中介者模式可以将这些复杂的交互集中到中介者对象中，简化对象之间的关系。例如在一个多人游戏中，玩家之间的交互、游戏状态的更新等复杂操作可以通过中介者来协调。当一个对象引用其他很多对象并且直接与这些对象通信，导致难以复用该对象时，中介者模式可以将对象之间的通信解耦，提高对象的复用性。当想定制一个分布在多个类中的行为，而又不想生成太多的子类时，中介者模式可以在中介者对象中集中处理这些行为，避免在多个类中重复实现相同的行为逻辑。典型应用：MVC（Model - View - Controller）架构中的 Controller 可以看作是中介者模式的应用，它协调 Model 和 View 之间的交互，处理用户输入、更新模型数据并通知视图进行更新。6. 备忘录模式（Memento）特征描述：在不破坏封装性的前提下，捕获一个对象的内部状态，并在该对象之外保存这个状态。重点是将对象具备的状态提出到备忘录中，以使不暴露原有对象的结构信息。应用场景：当必须保存一个对象在某一个时刻的（部分）状态，这样以后需要时它才能恢复到先前的状态时，备忘录模式可以方便地实现状态的保存和恢复。例如在一个文本编辑器中，保存文档的历史版本状态，以便用户可以撤销操作回到之前的版本。当如果用接口来让其他对象直接得到这些状态，将会暴露对象的实现细节并破坏对象的封装性时，备忘录模式通过备忘录对象来封装状态，保证了对象的封装性。典型应用：在 Web 开发中，ActionForm 可能会使用备忘录模式来保存表单数据的状态，以便在页面回退或表单提交失败时恢复数据。7. 观察者模式（Observer）特征描述：定义对象间的一种一对多的依赖关系，当一个对象的状态发生改变时，所有依赖于它的对象都得到通知并被自动更新。观察者要向被观察者注册，当状态发生变化时，观察者要迭代向所有观察者发消息。应用场景：当一个对象的状态发生改变，需要通知依赖它的多个对象时，观察者模式可以用于实现对象状态的监听和更新。例如在一个股票价格监控系统中，多个观察者（如投资者）关注股票价格（被观察者）的变化，当价格变化时，观察者会收到通知并进行相应的操作。当对象间存在依赖关系，但希望降低耦合度时，观察者模式通过通知机制来传递状态变化，而不直接暴露对象的内部状态，避免了对象间的紧密耦合。典型应用：监听器是观察者模式的常见应用，如在 Java 图形用户界面中，按钮点击事件的监听器，当按钮被点击（状态改变）时，注册的监听器会收到通知并执行相应的操作。8. 状态模式（State）特征描述：允许一个对象在其内部状态改变时改变它的行为，对象看起来似乎修改了它的类。将行为提取到状态中，对象行为通过状态实现。应用场景：当一个对象的行为取决于它的状态，并且它必须在运行时刻根据状态改变它的行为时，状态模式可以将不同状态下的行为封装到不同的状态类中，使代码结构更加清晰。例如在一个电梯控制系统中，电梯的运行状态（上升、下降、停止等）不同，其行为（如开门、关门、运行速度等）也不同，状态模式可以很好地处理这种情况。当一个操作中含有庞大的多分支的条件语句，且这些分支依赖于该对象的状态时，状态模式将每一个条件分支放入一个独立的类中，这使得可以根据对象自身的情况将对象的状态作为一个对象，该对象可以不依赖于其他对象而独立变化。例如在一个订单处理系统中，订单的状态（已提交、已付款、已发货等）不同，处理订单的操作也不同，使用状态模式可以避免复杂的条件判断。9. 策略模式（Strategy）特征描述：定义一系列的算法，把它们一个个封装起来，并且使它们可相互替换。在对象中包含算法对象，通过选择不同的算法对象来实现不同的行为。应用场景：当许多相关的类仅仅是行为有异时，“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。例如在一个游戏中，不同的角色可能有不同的攻击策略，通过策略模式可以方便地为每个角色配置不同的攻击算法。当需要使用一个算法的不同变体时，例如定义一些反映不同的空间/时间权衡的算法，策略模式可以轻松切换不同的算法实现。当算法使用客户不应该知道的数据时，可使用策略模式以避免暴露复杂的、与算法相关的数据结构。例如在一个加密算法库中，不同的加密策略可以封装起来，用户只需选择合适的加密策略，而无需了解算法内部的数据结构。当一个类定义了多种行为，并且这些行为在这个类的操作中以多个条件语句的形式出现时，将相关的条件分支移入它们各自的 Strategy 类中以代替这些条件语句，使代码更加清晰和易于维护。10. 模板模式（Template Method）特征描述：定义一个操作中的算法骨架，而将一些步骤延迟到子类中。模板定义逻辑骨架，由子类给出步骤中的实现，这样可以在保证算法整体结构不变的情况下，实现部分步骤的个性化定制。应用场景：当一次性实现一个算法的不变的部分，并将可变的行为留给子类来实现时，模板模式可以提高代码的复用性。例如在一个抽象的排序算法中，算法的整体框架（如排序的比较操作等）可以在父类中定义，而具体的排序方式（如冒泡排序、快速排序等）可以由子类实现，这样可以方便地扩展不同的排序算法。当各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复时，模板模式提供了一种有效的方式。例如在多个子类都需要进行初始化操作时，可以在父类中定义模板方法，在子类中实现具体的初始化步骤，避免了代码在多个子类中的重复编写。当需要控制子类扩展时，模板模式可以通过定义模板方法的流程和可扩展点，限制子类的扩展方式，确保子类在扩展时遵循一定的规则和结构。11. 访问者模式（Visitor）特征描述：表示一个作用于某对象结构中的各元素的操作，它在不改变各元素的类的前提下定义作用于这些元素的新操作。通过访问者模式，可以将对对象结构的操作与对象结构本身分离开来，使得在不修改对象结构类的情况下添加新的操作变得更加容易。应用场景：当一个对象结构包含很多类对象，它们有不同的接口，而想对这些对象实施一些依赖于其具体类的操作时，访问者模式可以提供一种统一的方式来处理不同类型的对象。例如在一个图形处理系统中，有不同类型的图形（圆形、矩形、三角形等），每个图形有自己的绘制方法，使用访问者模式可以定义一个访问者来实现对所有图形的统一操作，如计算图形面积总和等。当需要对一个对象结构中的对象进行很多不同的并且不相关的操作，而想避免让这些操作“污染”这些对象的类时，访问者模式可以将这些操作封装在访问者类中，而不是在对象类中添加大量的方法。例如在一个电商系统中，对于商品对象，可能有计算价格、统计库存、生成报表等不同操作，这些操作可以通过访问者模式来实现，而不影响商品类的简洁性。当该对象结构被很多应用共享时，用 Visitor 模式让每个应用仅包含需要用到的操作。这样可以根据不同应用的需求定制访问者，减少不必要的代码和资源占用。例如在一个企业级架构中，多个子系统可能共享一个数据结构，不同子系统可以通过各自的访问者来处理该数据结构，提高系统的灵活性和可维护性。当定义对象结构的类很少改变，但经常需要在此结构上定义新的操作时，访问者模式非常适用。改变对象结构类需要重定义对所有访问者的接口，这可能需要很大的代价。如果对象结构类经常改变，那么可能还是在这些类中定义这些操作较好。但如果结构相对稳定，访问者模式可以方便地添加新操作，而无需修改对象结构类。说明：本文基于经典的 GoF（Gang of Four）23 种设计模式整理，适用于 Java 及面向对象编程的一般场景。部分框架特性（如 Spring、Hibernate 等）的具体实现可能随版本更新有所调整，实际使用中请参考对应框架的最新官方文档。
Java 设计模式——观察者模式：从优衣库不使用新疆棉事件看系统的动态响应
背景与引言背景事件：近日，优衣库宣布不再使用新疆棉花，这一举措引发了广泛的社会讨论。消费者的反应和舆论的压力，让优衣库的决策迅速影响了市场和品牌形象。类似的，许多软件系统也面临着需要根据外部事件或状态的变化，做出即时反应的需求。在软件设计中，观察者模式（Observer Pattern） 正是为了处理这种“状态变化与反应”的需求而生。它允许一个对象（主题）在状态发生变化时，通知所有依赖它的对象（观察者），而不需要显式的调用，从而实现模块间的松耦合与动态更新。今天，我们将结合优衣库棉花事件这一业务场景，讲解如何使用观察者模式来应对复杂系统中的事件驱动响应。一、观察者模式概述1.1 观察者模式的定义观察者模式属于行为型设计模式（Behavioral Pattern）。其核心思想是：当对象的状态发生改变时，所有依赖于它的对象都会收到通知并自动更新。这种模式非常适用于系统中某一部分状态变化需要同时通知到其他部分的场景。1.2 观察者模式的组成部分主题（Subject）：状态变化的核心对象，负责管理所有的观察者并通知它们。观察者（Observer）：依赖于主题对象的变化，并作出响应。通知机制：通过注册和触发通知，观察者获得主题对象的状态变化。在优衣库不使用新疆棉的事件类比中，品牌决策就像主题对象，而消费者、舆论、媒体等就是观察者。每当品牌作出决策，消费者的情感、舆论的反应都会发生变化，观察者（消费者）的情绪或行为会随之改变。二、观察者模式与优衣库事件的类比2.1 事件背景假设优衣库公司决定不再使用新疆棉，这一决策会触发不同的消费者、股东、媒体等不同的观察者产生反应：消费者：可能对这一举措产生负面或正面的情绪，甚至会改变购买行为。媒体：可能会通过报道引导舆论。股东：可能对这一决策的影响做出反应，调整投资策略。这些变化是动态的，且每当优衣库做出决策时，所有相关的观察者（消费者、媒体等）都会收到通知并做出相应反应。2.2 在系统设计中的应用在类似的业务场景中，观察者模式非常有用。通过观察者模式，当主题（如优衣库品牌决策）发生变化时，所有需要响应变化的组件（如消费者、舆论）都会自动收到通知并执行相应的处理逻辑。这种设计使得不同的模块之间保持了松耦合的关系，不需要主题对象知道观察者的具体细节。三、观察者模式的实现接下来，我们通过代码来实现一个基于观察者模式的简单示例，模拟优衣库品牌决策引发的消费者反应。3.1 代码示例：实现观察者模式import java.util.ArrayList; import java.util.List; // 主题接口 interface BrandDecision { void addObserver(ConsumerObserver observer); void removeObserver(ConsumerObserver observer); void notifyObservers(); } // 具体主题：优衣库的品牌决策 class UniqloBrandDecision implements BrandDecision { private List<ConsumerObserver> observers = new ArrayList<>(); private String decision; @Override public void addObserver(ConsumerObserver observer) { observers.add(observer); } @Override public void removeObserver(ConsumerObserver observer) { observers.remove(observer); } @Override public void notifyObservers() { for (ConsumerObserver observer : observers) { observer.update(decision); } } public void makeDecision(String decision) { this.decision = decision; System.out.println("优衣库品牌做出决策：" + decision); notifyObservers(); } } // 观察者接口：消费者反应 interface ConsumerObserver { void update(String decision); } // 具体观察者：消费者 class Consumer implements ConsumerObserver { private String name; public Consumer(String name) { this.name = name; } @Override public void update(String decision) { System.out.println(name + " 收到决策通知，决定如何行动：" + decision); if (decision.contains("不使用新疆棉")) { System.out.println(name + " 表示失望，可能不再购买！"); } else { System.out.println(name + " 觉得决策不错，继续购买！"); } } }3.2 代码分析在上面的代码中：UniqloBrandDecision 类：作为主题，它包含了消费者的观察者列表。当优衣库作出决策时，会调用 makeDecision() 方法，通知所有的观察者（消费者）进行反应。Consumer 类：作为具体的观察者，当品牌决策改变时，它会根据决策内容做出不同的反应。3.3 客户端代码：模拟消费者反应public class ObserverPatternExample { public static void main(String[] args) { // 创建品牌决策主题 UniqloBrandDecision uniqloDecision = new UniqloBrandDecision(); // 创建消费者观察者 Consumer consumer1 = new Consumer("消费者 A"); Consumer consumer2 = new Consumer("消费者 B"); // 消费者注册到品牌决策 uniqloDecision.addObserver(consumer1); uniqloDecision.addObserver(consumer2); // 优衣库做出决策 uniqloDecision.makeDecision("优衣库决定不再使用新疆棉，改用其他地区棉花"); // 优衣库做出新的决策 uniqloDecision.makeDecision("优衣库决定继续使用新疆棉"); } }3.4 输出结果优衣库品牌做出决策：优衣库决定不再使用新疆棉，改用其他地区棉花 消费者 A 收到决策通知，决定如何行动：优衣库决定不再使用新疆棉，改用其他地区棉花 消费者 A 表示失望，可能不再购买！ 消费者 B 收到决策通知，决定如何行动：优衣库决定不再使用新疆棉，改用其他地区棉花 消费者 B 表示失望，可能不再购买！ 优衣库品牌做出决策：优衣库决定继续使用新疆棉 消费者 A 收到决策通知，决定如何行动：优衣库决定继续使用新疆棉 消费者 A 觉得决策不错，继续购买！ 消费者 B 收到决策通知，决定如何行动：优衣库决定继续使用新疆棉 消费者 B 觉得决策不错，继续购买！四、观察者模式的优势与应用4.1 优点松耦合：主题和观察者之间没有紧密的耦合关系。主题只知道它需要通知观察者，而不关心观察者的具体实现。动态响应：观察者可以根据主题的状态变化动态调整自己的行为，非常适合事件驱动的系统。易于扩展：添加新的观察者（如新消费者）非常方便，不需要改变现有的代码。4.2 缺点通知开销：如果观察者数量很多，通知机制可能会成为性能瓶颈。依赖顺序：观察者的顺序可能影响到通知的处理流程，某些场景下可能需要额外的控制。五、总结通过优衣库不使用新疆棉事件，我们可以清晰地看到观察者模式在应对复杂决策和动态反应中的优势。当品牌作出决策时，消费者和舆论等多个观察者会收到通知并做出反应，无缝的事件驱动机制让这种决策的传播变得高效且灵活。如果你的系统中涉及到多个模块需要对某一变化做出响应，观察者模式无疑是一个非常合适的解决方案。它能够帮助你保持系统的松耦合，快速响应变化，同时确保灵活的扩展性。希望这篇文章能够帮助你理解如何通过观察者模式应对复杂的业务决策和系统响应！如果你对其他设计模式的应用感兴趣，欢迎持续关注后续系列文章。
1. 装饰器模式概述装饰器（Decorator）设计模式属于典型的结构型模式（在 GoF 的模式分类中，模式分为创建型、结构型、行为模式三种）。其核心目的在于动态地为对象添加额外功能，这也是理解装饰器模式的关键所在。正如 GoF 在《Elements of Reusable Object-Oriented Software》中所概述："Decorator Pattern――Attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality."2. 应用场景：何时使用装饰器模式在 GoF 的相关书籍中，常以文本组件与边框的例子阐述装饰器模式。这里我们以一个更具说服力的“三明治”例子来说明其必要性。假设要为三明治小店构建程序，设计各种三明治对象。若采用传统继承方式，从简单的 Sandwich 对象开始，若要创建带蔬菜的三明治就继承原有 Sandwich 并添加蔬菜成员变量；后续若要添加咸肉、奶油等多种成分，且考虑各种成分的任意组合，那么继承方式将导致类的数量急剧膨胀。例如，若有 n 种成分，根据组合数学知识，不同组合方案数为 2^n 种（即 $C_{n}^0 + C_{n}^1 + \cdots + C_{n}^n$）。这意味着每增加一种成分，工作量可能成倍增加，同时类库也会变得极为庞大。而装饰器模式则能有效解决此类问题，通过组合而非继承来扩展功能。3. 设计与实现3.1 类结构设计Ingredient（成分）所有类的父类，一般为抽象类且方法有默认实现，也可为接口。它包含所有成分共有的方法，如描述自身的 getDescription 方法、获取价格的 getCost 方法，以及打印自身描述和价格的 printDescription 方法（构成模板方法）。Bread（面包）三明治中必需的两片面包，是系统中最基本元素，也是被装饰的元素，类似 IO 中的媒质流（原始流）。它是具体成分，需实现父类抽象方法，描述可通过构造器传入，价格简单返回固定值。Decorator（装饰器）所有其他成分（如猪肉、羊肉、青菜、芹菜等具有装饰功能成分）的父类。这是一个抽象类，其特征为必须有一个父类（Ingredient）作为成员变量，且必须继承公共父类（Ingredient）。Concrete Decorators（具体装饰器）如 Pork（猪肉）、Mutton（羊肉）、Celery（芹菜）、GreenGrocery（青菜）等。它们继承自 Decorator 类，通过构造器传入 Ingredient 实例。在实现 getDescription 和 getCost 方法时，不仅包含自身的描述和价格，还会加上被装饰成分的描述和价格，从而增强了被装饰成分的功能。3.2 代码实现以下是核心类的 Java 代码实现：Ingredient.javapublic abstract class Ingredient { public abstract String getDescription(); public abstract double getCost(); public void printDescription() { System.out.println(" Name " + this.getDescription()); System.out.println(" Price RMB " + this.getCost()); } }Bread.javapublic class Bread extends Ingredient { private String description; public Bread(String desc) { this.description = desc; } public String getDescription() { return description; } public double getCost() { return 2.48; } }Decorator.javapublic abstract class Decorator extends Ingredient { // 持有被装饰对象的引用 Ingredient ingredient; public Decorator(Ingredient igd) { this.ingredient = igd; } public abstract String getDescription(); public abstract double getCost(); }Pork.javapublic class Pork extends Decorator { public Pork(Ingredient igd) { super(igd); } public String getDescription() { String base = ingredient.getDescription(); return base + "\n" + "Decorated with Pork!"; } public double getCost() { double basePrice = ingredient.getCost(); double porkPrice = 1.8; return basePrice + porkPrice; } }注：其他羊肉、芹菜、青菜装饰器类的实现类似，仅在描述和价格的具体数值上有所不同。3.3 测试验证以下是测试类 DecoratorTest，通过创建不同组合的三明治来展示装饰器模式的动态组合功能：public class DecoratorTest { public static void main(String[] args) { // 夹羊肉的三明治 Ingredient compound = new Mutton(new Celery(new Bread("Master24's Bread"))); compound.printDescription(); // 全蔬菜的三明治 compound = new Celery(new GreenGrocery(new Bread("Bread with milk"))); compound.printDescription(); // 全荤的三明治 compound = new Mutton(new Pork(new Bread("Bread with cheese"))); compound.printDescription(); } }4. 结构剖析与经典案例4.1 通用结构在软件设计中，常使用 UML 图来表示结构。装饰器模式的通用结构图包含以下几个关键部分：Component：位于装饰器模式结构图的顶层，是定义装饰器模式中公共方法的类（对应例子中的 Ingredient）。ConcreteComponent：是具体被装饰的类，类似于 IO 包中的媒体流，在“三明治”例子中就是 Bread 类。Decorator：装饰器模式的核心对象，是所有具体装饰器对象的父类。它继承自 Component，同时包含一个 Component 作为其成员变量，通过这种方式实现动态地增加功能。ConcreteDecoratorA 和 ConcreteDecoratorB：是两个具体的装饰器对象，完成具体的装饰功能。装饰功能的实现是通过调用被装饰对象对应的方法，再加上装饰对象自身的方法。关于透明与不透明装饰器：在实际情况中，存在“透明装饰器”和“不透明装饰器”之分。透明装饰器：要求整个 Decorator 结构中的所有类保持相同的“接口”（即共同方法）。不透明装饰器：其“接口”在某些子类中会得到增强，具体取决于该类与顶层抽象类或接口是否有相同的公共方法。例如 IO 中的 ByteArrayInputStream 比 InputStream 抽象类多一些方法，所以 IO 中的装饰器通常被视为“不透明装饰器”。4.2 Java IO 中的装饰器Java IO 包是装饰器模式的经典应用场景。以输入字节流部分为例，其结构如下：InputStream：是装饰器的顶层类，为抽象类，包含一些共有的方法，如读方法（read，有 3 个不同参数的重载形式）、关闭流的方法（close）、mark 相关的方法（mark、reset 和 markSupported）、跳跃方法（skip）、查询是否还有元素方法（available）。FileInputStream、PipedInputStream 等：是具体的被装饰对象，它们的“接口”中一般有额外的方法。FilterInputStream：是装饰器中的核心，即 Decorator 对象。DataInputStream、BufferedInputStream 等：是具体的装饰器，它们保持了和 InputStream 同样的接口。ObjectInputStream：是 IO 字节输入流中特殊的装饰器，它不是 FilterInputStream 的子类，但和其他 FilterInputStream 的子类功能相似，都可以装饰其他对象。注：IO 包中的输出字节流、输入字符流和输出字符流也都采用了装饰器模式。5. 总结与启示装饰器模式为我们提供了一种灵活且高效的方式来扩展对象功能，避免了因大量使用继承而导致的类爆炸问题。通过动态组合装饰器对象，可以根据需求在运行时为对象添加各种功能，提高了代码的可维护性和扩展性。在实际软件开发中，当遇到需要为对象添加多种可选功能且不希望通过复杂的继承层次来实现时，装饰器模式是一个值得考虑的优秀设计模式。它能够让我们的代码结构更加清晰，同时也便于应对不断变化的功能需求。说明：本文基于经典 Java IO 流架构讲解装饰器模式，该模式在 Java 标准库中长期稳定存在（适用于 Java 1.0+ 及后续版本）。虽然现代 Java 开发中 NIO 应用广泛，但理解 IO 中的装饰器模式对于掌握设计模式思想依然具有重要价值。
羽绒服割不动年轻人？Java代码教你“穿”出新选择！
羽绒服割不动年轻人？Java 代码教你“穿”出新选择！在这个寒冷的冬天，羽绒服市场的变化如同编程语言的更新迭代一样引人注目。国产羽绒服价格飙升，让追求性价比的年轻人望而却步。正如羽绒服不再是年轻人的唯一保暖选择，在编程世界里，实现功能的方式也多种多样。今天，我们将通过 Java 代码探索如何让技术选型更加多样化，就像年轻人寻找羽绒服的“平替”一样，在保证核心功能的前提下追求更高的性价比。1. 羽绒服市场现状：品牌溢价与消费者选择如今，国产羽绒服的高端化转型使得价格一路看涨。除了原材料涨价的因素外，品牌溢价也占据了不小的比重。这让年轻人开始另寻他法：冲锋衣、防风服等成为了热门替代选择，甚至翻新旧羽绒服也成为一种趋势。在编程领域，Java 作为一种广泛应用的编程语言，也面临着类似的情况。开发者们在选择技术方案时，不再局限于传统的、可能成本较高（性能、资源等方面）的方式，而是寻求更高效、更经济的“替代方案”。2. Java 中的“保暖衣物”选择：接口与实现类在 Java 中，接口（Interface） 就像是一种标准规范，它定义了一组方法的签名，但不包含方法的实现。而 实现类（Implementation Class） 则是根据这个接口的规范来具体实现接口中的方法。就如同不同品牌的保暖衣物都遵循“保暖”这一基本功能标准一样。2.1 定义保暖衣物接口public interface WarmClothing { void keepWarm(); }这个 WarmClothing 接口定义了一个 keepWarm 方法，任何实现这个接口的类都必须提供这个方法的具体实现。2.2 羽绒服类（传统选择）public class DownJacket implements WarmClothing { @Override public void keepWarm() { System.out.println("羽绒服提供保暖功能，但价格较高。"); } }这是传统的羽绒服类，它实现了 WarmClothing 接口，提供了保暖功能。在我们的类比中，它代表了价格较高（或资源消耗较大）的传统选择。2.3 冲锋衣类（“平替”选择）public class Windbreaker implements WarmClothing { @Override public void keepWarm() { System.out.println("冲锋衣也能提供保暖功能，且性价比更高。"); } }冲锋衣类同样实现了 WarmClothing 接口。它就像是年轻人在 Java 编程中寻找的更具性价比的“平替”方案，能够以较低的成本（资源、性能等方面）实现相同的保暖（功能）需求。3. 选择合适的“衣物”：多态性的应用在 Java 中，多态性（Polymorphism） 允许我们根据对象的实际类型来调用相应的方法。这就好比在不同的天气和场景下，我们可以根据实际需求选择穿羽绒服还是冲锋衣。public class Main { public static void main(String[] args) { // 选择羽绒服 WarmClothing clothing1 = new DownJacket(); clothing1.keepWarm(); // 选择冲锋衣 WarmClothing clothing2 = new Windbreaker(); clothing2.keepWarm(); } }在上述代码中，我们创建了 DownJacket 和 Windbreaker 的对象，并将它们赋值给 WarmClothing 类型的变量。通过这种方式，我们可以根据需要灵活地选择不同的实现类，而无需修改调用代码。这就像我们在面对不同价格和性能的保暖衣物时，可以根据自己的需求（预算、保暖程度等）进行选择。4. 进一步优化：策略模式在实际应用中，我们可能需要根据更多的条件来选择合适的“保暖衣物”（实现类）。这时，我们可以引入 策略模式（Strategy Pattern） 来使代码更加灵活和可维护。4.1 定义策略接口public interface ClothingStrategy { WarmClothing chooseClothing(); }这个接口定义了一个 chooseClothing 方法，用于返回一个 WarmClothing 类型的对象。4.2 羽绒服策略类public class DownJacketStrategy implements ClothingStrategy { @Override public WarmClothing chooseClothing() { return new DownJacket(); } }这个类实现了 ClothingStrategy 接口，返回一个 DownJacket 对象。4.3 冲锋衣策略类public class WindbreakerStrategy implements ClothingStrategy { @Override public WarmClothing chooseClothing() { return new Windbreaker(); } }同样，这个类返回一个 Windbreaker 对象。4.4 使用策略模式选择衣物public class Main { public static void main(String[] args) { // 根据策略选择羽绒服 ClothingStrategy strategy1 = new DownJacketStrategy(); WarmClothing clothing1 = strategy1.chooseClothing(); clothing1.keepWarm(); // 根据策略选择冲锋衣 ClothingStrategy strategy2 = new WindbreakerStrategy(); WarmClothing clothing2 = strategy2.chooseClothing(); clothing2.keepWarm(); } }通过策略模式，我们可以将选择逻辑封装在不同的策略类中，使得代码更加清晰和易于扩展。如果以后有新的“保暖衣物”（实现类）出现，只需要创建一个新的策略类即可，而不会影响到其他代码。结语在 Java 编程中，就像年轻人在保暖衣物市场中寻找性价比高的选择一样，我们可以通过合理运用 接口、多态性 和 策略模式 等特性，在不同的需求和场景下选择最合适的技术方案。这有助于避免不必要的“品牌溢价”（性能、资源浪费等），实现高效、灵活的编程。希望这篇文章能帮助你在 Java 编程的道路上，像年轻人选择适合自己的保暖衣物一样，做出明智的技术决策！
门面设计模式门面设计模式（Facade Pattern）在 Tomcat 中有多处应用，例如 Request 和 Response 对象的封装、Standard Wrapper 到 ServletConfig 的封装、以及 ApplicationContext 到 ServletContext 的封装等。门面设计模式的原理门面设计模式的核心作用在于封装。顾名思义，就是将复杂的内部结构封装成一个统一的“门面”，以便与其他模块更便捷地进行交互，类似于国家外交部统一对外接口的角色。该模式主要应用于由多个子系统组成的大型系统中。子系统之间必然涉及相互通信，但每个子系统不应将内部数据过多地暴露给其他系统，否则划分子系统的意义将大打折扣。因此，每个子系统都会设计一个门面，将外部感兴趣的数据封装起来，并通过这个门面提供访问接口。这就是门面设计模式存在的意义。门面设计模式示意图如下：客户端（Client）仅能访问 Facade 提供的数据，这是门面设计模式的关键所在。至于 Client 如何访问 Facade，以及 Subsystem 如何提供 Facade，该模式并没有严格规定。Tomcat 的门面设计模式示例Tomcat 中包含众多不同组件，组件间需要相互交互数据，使用门面模式隔离数据是一种有效的方法。以下是 Request 对象上使用的门面设计模式示例：从类图可以看出，HttpRequestFacade 类封装了 HttpRequest 接口能够提供的数据。通过 HttpRequestFacade 访问到的数据都被代理到 HttpRequest 中。通常被封装的对象会被设为 Private 或者 Protected 访问修饰符，以防止在 Facade 之外被直接访问。观察者设计模式观察者设计模式（Observer Pattern）是一种常用的设计方法，通常也称为发布 - 订阅模式（Publish-Subscribe Pattern）或事件监听机制。它通常在某个事件发生的前后触发一些特定操作。观察者模式的原理观察者模式的原理相对简单：当主体（Subject）状态发生变化时，依赖该主体的观察者（Observer）会收到通知并执行相应操作。观察者必须向主体注册（登记），否则主体无法通知它。观察者模式通常包含以下几个角色：Subject（抽象主题）：负责管理所有观察者的引用，同时定义主要的事件操作。ConcreteSubject（具体主题）：实现抽象主题定义的所有接口，当自身状态发生变化时，通知所有观察者。Observer（观察者）：定义监听主题发生变化时的相应操作接口。Tomcat 的观察者模式示例Tomcat 中多处使用了观察者模式。前面提到的控制组件生命周期的 Lifecycle 接口就是这种模式的体现，此外 Servlet 实例的创建、Session 的管理、Container 的管理等都采用了同样的原理。下面主要看一下 Lifecycle 的具体实现。Lifecycle 的观察者模式结构图如下：在上述结构图中：LifecycleListener 代表抽象观察者，它定义了一个 lifecycleEvent 方法，这是当主题变化时要执行的方法。ServerLifecycleListener 代表具体观察者，它实现了 LifecycleListener 接口的方法。Lifecycle 接口代表抽象主题，它定义了管理观察者的方法以及其他相关方法。StandardServer 代表具体主题，它实现了抽象主题的所有方法。Tomcat 对观察者模式做了扩展，增加了两个辅助类：LifecycleSupport 和 LifecycleEvent。LifecycleEvent 使得可以定义事件类别，不同的事件可区别处理，更加灵活。LifecycleSupport 类代理了主题对多观察者的管理，将这个管理逻辑抽出来统一实现。以后如果需要修改，只需修改 LifecycleSupport 类，而不需要修改所有具体主题，因为所有具体主题对观察者的操作都被代理给了 LifecycleSupport 类。这可以认为是观察者模式的一种改进版。LifecycleSupport 调用观察者的方法代码如下：public void fireLifecycleEvent(String type, Object data) { LifecycleEvent event = new LifecycleEvent(lifecycle, type, data); LifecycleListener interested[] = null; synchronized (listeners) { interested = (LifecycleListener[]) listeners.clone(); } for (int i = 0; i < interested.length; i++) { interested[i].lifecycleEvent(event); } }主题是如何通知观察者的呢？请看以下代码示例：public void start() throws LifecycleException { lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null); lifecycle.fireLifecycleEvent(START_EVENT, null); started = true; synchronized (services) { for (int i = 0; i < services.length; i++) { if (services[i] instanceof Lifecycle) { ((Lifecycle) services[i]).start(); } } } lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null); }命令设计模式在 Tomcat 架构中，核心组件 Connector 和 Container 的交互体现了命令设计模式。Connector 将接收到的请求封装为命令，传递给 Container 进行处理。命令模式的原理命令模式（Command Pattern）的主要作用是将命令封装，把发出命令的责任和执行命令的责任分离开，实现功能分工。不同的模块可以对同一个命令做出不同的解释。命令模式通常包含以下几个角色：Client（客户端）：创建一个命令，并决定接受者。Command（命令）：命令接口，定义一个抽象方法。ConcreteCommand（具体命令）：负责调用接受者的相应操作。Invoker（请求者）：负责调用命令对象执行请求。Receiver（接受者）：负责具体实施和执行一次请求。Tomcat 中的命令模式示例Tomcat 作为应用服务器，会接收到很多请求，如何分配和执行这些请求是核心功能之一。命令模式在 Connector 和 Container 组件之间得到了体现。以下是 Tomcat 命令模式的结构图：在此结构中：Connector 作为抽象请求者，HttpConnector 作为具体请求者。HttpProcessor 作为命令。Container 作为命令的抽象接受者，ContainerBase 作为具体的接受者。客户端即应用服务器 Server 组件。Server 首先创建命令请求者 HttpConnector 对象，然后创建命令 HttpProcessor 对象，再把命令对象交给命令接受者 ContainerBase 容器来处理。命令最终是被 Tomcat 的 Container 执行的。命令可以以队列的方式进入，Container 也可以以不同的方式来处理请求，例如 HTTP/1.0 协议和 HTTP/1.1 的处理方式会有所不同。责任链模式责任链模式（Chain of Responsibility Pattern）是 Tomcat 中最容易发现的设计模式之一，也是 Tomcat 中 Container 设计的基础。整个容器通过一条链连接在一起，这条链一直将请求正确地传递给最终处理请求的 Servlet。责任链模式的原理责任链模式将很多对象通过每个对象对其下家的引用连接起来形成一条链。请求在这条链上传递，直到链上的某个对象处理此请求；或者每个对象都可以处理请求，并传给下一家，直到最终链上每个对象都处理完。这样可以在不影响客户端的情况下，在链上增加任意的处理节点。通常责任链模式包含以下几个角色：Handler（抽象处理者）：定义一个处理请求的接口。ConcreteHandler（具体处理者）：处理请求的具体类，或者传给下家。Tomcat 中责任链模式示例在 Tomcat 中，这种设计模式几乎被完整地使用。Tomcat 的容器设置就是责任链模式，从 Engine 到 Host，再到 Context，一直到 Wrapper，都是通过一个链传递请求。Tomcat 中责任链模式的类结构图如下：上图基本描述了四个子容器使用责任链模式的类结构。对应的责任链模式角色中，Container 扮演抽象处理者角色，具体处理者由 StandardEngine 等子容器扮演。与标准的责任链不同的是，这里引入了 Pipeline 和 Valve 接口。它们有什么作用呢？实际上，Pipeline 和 Valve 扩展了这个链的功能，使得在链往下传递过程中，能够接受外界的干预。Pipeline 如同连接每个子容器的管道，里面传递的 Request 和 Response 对象好比管子里流的水；而 Valve 则是这个管子上开的一个个小口子（阀门），让你有机会能够接触到里面的水，做一些额外的事情。为了防止水被引出来而不能流到下一个容器中，每一段管子最后总有一个节点保证它一定能流到下一个子容器。所以每个容器都有一个 StandardXXXValve。只要涉及到这种链式处理流程，这是一个非常值得借鉴的模式。说明：本文涉及的部分类名（如 HttpConnector、HttpProcessor）主要基于 Tomcat 5/6 版本架构。在 Tomcat 7 及更高版本中，Connector 和 Container 的内部实现有所调整（例如移除了 HttpConnector），但整体设计模式思想（门面、观察者、命令、责任链）依然适用。
一、引子在 Java 中，String 类型具有一些独特的特性。其一，String 对象一旦被创建就不可改变（Immutable）；其二，当两个 String 对象所包含的内容相同时，JVM 通常只会创建一个 String 对象，这两个不同的对象引用将指向该同一对象。我们可以通过以下代码来验证这两个特性：public class TestPattern { public static void main(String[] args) { String n = "I Love Java"; String m = "I Love Java"; System.out.println(n == m); } }上述代码会输出 true，这表明在 JVM 中 n 和 m 两个引用指向了同一个 String 对象。若在系统输出之前添加一行代码 m = m + "hehe";，此时 n == m 的结果将变为 false。这是因为执行添加语句时，m 指向了一个新创建的 String 对象，而非修改原来引用的对象（因为 String 不可变）。String 类型的设计避免了创建大量相同内容的 String 对象时产生的不必要资源损耗，是享元模式（Flyweight Pattern）应用的范例。下面让我们深入学习享元模式。二、定义与分类享元模式英文称为"Flyweight Pattern"。其定义为：采用共享技术来避免大量拥有相同内容对象的开销，这里最常见、直观的开销就是内存损耗。享元模式以共享的方式高效支持大量细粒度对象。在该模式中，核心概念是共享。为实现共享，需要区分内蕴状态（Intrinsic State）和外蕴状态（Extrinsic State）：内蕴状态：是共性，存储在享元内部，不会随环境改变而不同，可共享。外蕴状态：是个性，随环境改变而改变，由客户端保持。在具体环境下，客户端将外蕴状态传递给享元以创建不同对象。根据《Java 与模式》，享元模式分为单纯享元模式和复合享元模式。（一）单纯享元模式结构抽象享元角色（Flyweight）：在 Java 中可由抽象类或接口担当，为具体享元角色规定必须实现的方法，外蕴状态以参数形式通过此方法传入。具体享元角色（Concrete Flyweight）：实现抽象角色规定的方法，若存在内蕴状态，负责为其提供存储空间。享元工厂角色（Flyweight Factory）：负责创建和管理享元角色，是实现共享的关键。其实现通常使用单例模式（Singleton），确保工厂对象只产生一个实例。客户端角色（Client）：维护对所有享元对象的引用，并存储对应的外蕴状态。单纯享元模式的类图如下：[此处可插入单纯享元模式类图，展示 Client、Flyweight、FlyweightFactory、ConcreteFlyweight 之间的关系]该模式结构类似简单工厂模式，但重点不同。简单工厂模式主要使系统不依赖于实现细节，而享元模式旨在采用共享技术避免大量相同内容对象的开销。举例（以咖啡店订单为例）假设一家咖啡店有多种口味的咖啡（如拿铁、摩卡、卡布奇诺等），接到大量订单时，咖啡口味可设置为共享，不必为每一杯单独生成对象。以下是相关代码实现：import java.util.*; // 抽象订单类 public abstract class Order { // 执行卖出动作 public abstract void sell(); } // 具体口味订单类 public class FlavorOrder extends Order { public String flavor; // 获取咖啡口味 public FlavorOrder(String flavor) { this.flavor = flavor; } @Override public void sell() { System.out.println("卖出一份" + flavor + "的咖啡。"); } } // 口味工厂类 public class FlavorFactory { private Map<String, Order> flavorPool = new HashMap<>(); // 静态工厂，负责生成订单对象 private static final FlavorFactory flavorFactory = new FlavorFactory(); private FlavorFactory() { } public static FlavorFactory getInstance() { return flavorFactory; } public Order getOrder(String flavor) { Order order = flavorPool.get(flavor); if (order == null) { order = new FlavorOrder(flavor); flavorPool.put(flavor, order); } return order; } public int getTotalFlavorsMade() { return flavorPool.size(); } } // 客户端类 public class Client { // 客户下的订单 private static List<Order> orders = new ArrayList<>(); // 订单对象生成工厂 private static FlavorFactory flavorFactory; // 增加订单 private static void takeOrders(String flavor) { orders.add(flavorFactory.getOrder(flavor)); } public static void main(String[] args) { // 订单生成工厂 flavorFactory = FlavorFactory.getInstance(); // 增加订单 takeOrders("摩卡"); takeOrders("卡布奇诺"); takeOrders("香草星冰乐"); takeOrders("香草星冰乐"); takeOrders("拿铁"); takeOrders("卡布奇诺"); takeOrders("拿铁"); takeOrders("卡布奇诺"); takeOrders("摩卡"); takeOrders("香草星冰乐"); takeOrders("卡布奇诺"); takeOrders("摩卡"); takeOrders("香草星冰乐"); takeOrders("拿铁"); takeOrders("拿铁"); // 卖咖啡 for (Order order : orders) { order.sell(); } // 打印生成的订单 java 对象数量 System.out.println("\n客户一共买了 " + orders.size() + " 杯咖啡! "); // 打印生成的订单 java 对象数量 System.out.println("共生成了 " + flavorFactory.getTotalFlavorsMade() + " 个 FlavorOrder java 对象! "); } }输出结果显示，通过口味共享极大减少了对象数目，降低了内存消耗。例如，客户一共买了 15 杯咖啡，但只生成了 4 个 FlavorOrder Java 对象。（二）复合享元模式结构抽象享元角色：同单纯享元模式，为具体享元角色规定必须实现的方法，外蕴状态以参数形式传入。具体享元角色：实现抽象角色规定的方法，负责内蕴状态的存储空间（若有）。复合享元角色（Concrete Composite Flyweight）：所代表对象不可共享，但可分解为多个单纯享元对象的组合。享元工厂角色：负责创建和管理享元角色，实现共享的关键。客户端角色：维护对所有享元对象的引用，并存储对应的外蕴状态。复合享元模式的类图如下：[此处可插入复合享元模式类图，展示 Client、Flyweight、FlyweightFactory、ConcreteFlyweight、ConcreteCompositeFlyweight 之间的关系]该模式左半部类似简单工厂模式，右半部类似合成模式（Composite Pattern）。合成模式用于将具体享元角色和复合享元角色同等对待和处理，确保复合享元中包含的单纯享元具有相同外蕴状态，而单纯享元内蕴状态往往不同。举例（在餐馆点菜场景下）以去餐馆吃饭为例，内蕴状态代表菜肴种类，外蕴状态是点菜人。首先定义抽象享元角色：import java.util.*; interface Menu { // 规定实现类必须实现设置内外关系的方法 public void setPersonMenu(String person, List<String> list); // 规定实现类必须实现查找外蕴状态对应的内蕴状态的方法 public List<String> findPersonMenu(String person, List<String> list); }具体享元角色实现：class PersonMenu implements Menu { private String dish; // 在构造方法中给内蕴状态赋值 public PersonMenu(String dish) { this.dish = dish; } public synchronized void setPersonMenu(String person, List<String> list) { list.add(person); list.add(dish); } public List<String> findPersonMenu(String person, List<String> list) { List<String> dishList = new ArrayList<>(); Iterator<String> it = list.iterator(); while (it.hasNext()) { if (person.equals(it.next())) { dishList.add(it.next()); } } return dishList; } }享元工厂角色：class FlyweightFactory { private Map<String, Menu> menuList = new HashMap<>(); private static final FlyweightFactory factory = new FlyweightFactory(); // 单例模式，确保工厂对象唯一 private FlyweightFactory() { } public static FlyweightFactory getInstance() { return factory; } // 享元模式关键方法，根据内蕴状态创建或获取对象 public synchronized Menu factory(String dish) { if (menuList.containsKey(dish)) { return menuList.get(dish); } else { Menu menu = new PersonMenu(dish); menuList.put(dish, menu); return menu; } } // 验证生成对象数量 public int getNumber() { return menuList.size(); } // 支持复合享元创建的重载方法 public Menu factory(String[] dish) { PersonMenuMuch menu = new PersonMenuMuch(); for (String key : dish) { menu.add(key, this.factory(key)); } return menu; } }复合享元角色：class PersonMenuMuch implements Menu { private Map<String, Menu> MenuList = new HashMap<>(); public PersonMenuMuch() { } // 增加一个新的单纯享元对象 public void add(String key, Menu menu) { MenuList.put(key, menu); } // 两个无为的方法（因为复合享元不涉及内外状态对应） public synchronized void setPersonMenu(String person, List<String> list) { } public List<String> findPersonMenu(String person, List<String> list) { return null; } }客户端使用示例：class Client { private static FlyweightFactory factory; public static void main(String[] args) { List<String> list1 = new ArrayList<>(); factory = FlyweightFactory.getInstance(); Menu list = factory.factory("尖椒土豆丝"); list.setPersonMenu("ai92", list1); list = factory.factory("红烧肉"); list.setPersonMenu("ai92", list1); list = factory.factory("地三鲜"); list.setPersonMenu("ai92", list1); list = factory.factory("地三鲜"); list.setPersonMenu("ai92", list1); list = factory.factory("红焖鲤鱼"); list.setPersonMenu("ai92", list1); list = factory.factory("红烧肉"); list.setPersonMenu("ai921", list1); list = factory.factory("红焖鲤鱼"); list.setPersonMenu("ai921", list1); list = factory.factory("地三鲜"); list.setPersonMenu("ai921", list1); System.out.println(factory.getNumber()); List<String> list2 = list.findPersonMenu("ai921", list1); if (list2 != null) { Iterator<String> it = list2.iterator(); while (it.hasNext()) { System.out.println(" " + it.next()); } } } }（三）两种模式对比对比维度单纯享元模式复合享元模式复杂度较低较高，比单纯享元模式复杂共享效果良好，显著减少对象数量未达预期，虽内部单纯享元可共享，但复合享元角色本身使用 Map 保存状态，未节省空间和对象个数建议推荐使用违背享元模式初衷，应尽量使用单纯享元模式三、使用优缺点（一）优点享元模式能大幅降低内存中对象数量，提高程序运行速度。例如在处理大量重复字符串或文本系统中字母对象时可节省资源。（二）缺点为实现对象共享，需将一些状态外部化，使程序逻辑复杂化。读取外部状态会使运行时间稍变长。（三）使用条件系统中有大量对象，影响系统效率。对象状态可分离为内外两部分，且内外状态划分及对应关系维护很重要。划分不当可能无法减少对象数量，对应关系维护需花费一定空间和时间。享元模式是以时间换空间，可使用 B 树等优化对应关系查找。四、总结享元模式较为复杂，实际应用相对较少，但共享思想对系统优化有益，在企业级架构设计中应用广泛，如缓存体系。Java 中的 String 和 Integer 类是其应用实例。说明：本文代码示例旨在阐述享元模式的核心逻辑与结构。部分代码采用了较早期的 Java 编码风格（如部分裸类型使用），在实际现代 Java 开发中，建议严格使用泛型（Generics）并遵循最新的编码规范。
策略模式概述定义与概念策略模式（Strategy Pattern）属于对象行为型模式。其核心思想是针对一组算法，将每个算法封装到具有共同接口的独立类中，从而实现它们之间的互相替换。该模式将算法的责任和算法本身分隔开，委派给不同的对象管理，把算法包装到一系列的策略类里面，作为一个抽象策略类的子类。简单来说，就是准备一组算法，并将每一个算法封装起来，使它们可以互换。模式角色环境类（Context）：持有一个 Strategy 类的引用。它是策略模式的使用场景，负责调用具体策略类的算法接口来完成特定的任务。例如，在一个游戏中，游戏角色的移动行为可以看作是一个环境，它可能持有不同移动策略（如步行策略、飞行策略等）的引用，根据游戏场景的不同选择合适的移动策略。抽象策略类（Strategy）：这是一个抽象角色，通常由一个接口或抽象类实现。此角色给出了所有具体策略类所需要的接口，定义了算法的规范。具体的策略类必须实现这个接口，以保证算法的一致性和可替换性。具体策略类（Concrete Strategy）：包装了相关的算法和行为，实现了抽象策略类定义的接口，提供了具体的算法实现。比如，在上述游戏中，步行策略类和飞行策略类就是具体策略类，它们分别实现了移动算法的不同方式。代码实现环境类（Context）public class Context { // 持有策略类的引用 private Strategy strategy; // 设置策略类的方法 public void setStrategy(Strategy strategy) { this.strategy = strategy; } // 调用策略类的算法接口 public void contextInterface() { strategy.strategyInterface(); } }抽象策略类（Strategy）public abstract class Strategy { // 抽象的算法接口 public abstract void strategyInterface(); }具体策略类（ConcreteStrategy）public class ConcreteStrategyA extends Strategy { @Override public void strategyInterface() { // 实现具体的算法 A System.out.println("执行算法 A"); } } public class ConcreteStrategyB extends Strategy { @Override public void strategyInterface() { // 实现具体的算法 B System.out.println("执行算法 B"); } }特点与优势算法独立性与可扩展性策略模式使得每个算法都独立封装在一个类中，与其他算法互不干扰。当需要添加新的算法时，只需要创建一个新的具体策略类并实现抽象策略接口即可，不会影响到现有的代码结构。例如，如果在游戏中要添加一种新的移动方式，如游泳策略，只需要创建一个 SwimmingStrategy 类并实现 Strategy 接口。高内聚低耦合策略模式实现了高内聚低耦合。每个策略类专注于实现自己的算法，职责单一，内聚性高。同时，环境类与具体策略类之间通过抽象策略接口耦合，降低了它们之间的依赖关系，使得系统更加灵活和易于维护。当业务需求发生变化时，只需要调整或替换相应的策略类，而不需要大规模修改环境类或其他相关代码。应用场景游戏开发中的行为控制如前面提到的游戏角色的移动、攻击等行为可以采用策略模式。不同的角色可能有不同的攻击策略（如近战攻击、远程攻击等），通过策略模式可以方便地实现和切换这些攻击方式，提高游戏的可玩性和灵活性。电商系统中的促销策略在电商系统中，根据不同的促销活动，商品的计价方式可能不同。例如，有满减策略、折扣策略、赠品策略等。使用策略模式可以将这些不同的计价算法封装成具体策略类，根据促销活动的规则选择合适的策略来计算商品价格，方便系统的扩展和维护。代理模式概述概念解释代理模式可以用生活中的例子来理解，比如“我很忙，忙得没空理你，那你要找我呢就先找我的代理人吧”。在这个场景中，代理人和被代理人具备同一个接口，代理人虽然不能直接干活，但是知道被代理的人能做哪些事情、不能做哪些事情。代理模式就是在这种情况下，通过代理对象来控制对真实对象的访问。模式结构抽象主题角色（Subject）：声明了代理主题和真实主题的共同接口，这样一来在任何可以使用真实主题的地方都可以使用代理主题。它定义了真实主题和代理主题都需要实现的方法，使得客户端可以以统一的方式调用真实主题和代理主题的操作。代理主题角色（Proxy）：代理主题角色内部含有对真实主题的引用，从而可以在任何时候操作真实的主题对象。它提供一个与真实主题角色相同的接口，以便在任何时候都可以替代真实的主体，控制对真实主题的引用，负责在需要的时候创建真实主题对象。代理角色通常在将客户端调用传递给真实的主题之前或者之后都要执行一些额外的操作，比如权限验证、日志记录等。真实主题角色（RealSubject）：定义了代理角色所代表的真实对象，实现了抽象主题角色声明的接口，提供了真实的业务逻辑和功能实现。代码实现（以简单的图片加载为例）抽象主题角色（Image 接口）public interface Image { void display(); }真实主题角色（RealImage 类）public class RealImage implements Image { private String fileName; public RealImage(String fileName) { this.fileName = fileName; loadFromDisk(fileName); } private void loadFromDisk(String fileName) { System.out.println("正在从磁盘加载图片：" + fileName); } @Override public void display() { System.out.println("显示图片：" + fileName); } }代理主题角色（ProxyImage 类）public class ProxyImage implements Image { private RealImage realImage; private String fileName; public ProxyImage(String fileName) { this.fileName = fileName; } @Override public void display() { if (realImage == null) { realImage = new RealImage(fileName); } realImage.display(); } }应用场景远程代理（Remote Proxy）在分布式系统中，当客户端需要访问远程对象时，可以使用代理模式。代理对象位于客户端，它代表远程对象接收客户端的请求，并将请求转发给远程对象，然后将远程对象的响应返回给客户端。例如，在一个企业级应用中，客户端可能需要访问位于远程服务器上的数据库对象，通过远程代理可以隐藏网络通信的细节，使得客户端可以像访问本地对象一样访问远程数据库对象。虚拟代理（Virtual Proxy）当创建一个对象的开销较大时，可以使用虚拟代理。虚拟代理先创建一个占位符对象，当真正需要使用对象时才创建实际对象。比如在一个图片浏览应用中，如果图片很大，一次性加载所有图片会消耗大量内存和时间。可以使用虚拟代理，先显示一个占位图片，当用户点击图片时再真正加载高清图片，提高应用的性能和用户体验。保护代理（Protection Proxy）用于控制对真实对象的访问权限。例如，在一个企业内部系统中，有一些敏感数据的访问需要特定权限。保护代理可以在代理对象中进行权限验证，只有具有相应权限的用户才能访问真实对象的相关操作，增强系统的安全性。优势增强系统安全性通过保护代理，可以在代理对象中添加权限验证等安全机制，防止非法访问真实对象，确保系统的安全性和数据的保密性。例如，在企业资源规划（ERP）系统中，只有具有特定权限的管理员才能访问和修改关键业务数据，普通员工只能通过代理对象进行有限的查询操作。提高系统性能利用虚拟代理，能够延迟对象的创建，避免在不必要的时候创建复杂或资源消耗大的对象，从而节省系统资源，提高性能。如在大型游戏中，场景中的一些复杂模型可能只有在玩家靠近时才需要详细渲染，使用虚拟代理可以在玩家距离较远时仅用简单的占位模型代替，减少计算资源的占用，提高游戏的帧率和流畅度。实现解耦与灵活性代理模式将客户端与真实对象解耦，客户端只与代理对象交互，而代理对象负责管理真实对象的生命周期和访问控制。这样，当真实对象的实现发生变化时，只要代理对象的接口保持不变，客户端代码无需修改，提高了系统的灵活性和可维护性。例如，在一个电商系统中，如果商品库存管理模块的内部实现发生改变，只要库存查询代理对象的接口不变，与该代理对象交互的订单处理模块等其他部分就不需要调整。模式对比策略模式与代理模式在结构上虽有相似之处（都涉及封装与委托），但在设计意图与应用场景上存在显著差异。对比维度策略模式 (Strategy)代理模式 (Proxy)目的实现算法的可替换性。将不同的算法封装在独立的类中，使得算法的变化不会影响到使用算法的客户端代码。侧重于算法的多样化和切换。控制对真实对象的访问。通过代理对象来提供额外的功能（如权限控制、性能优化等），同时隐藏真实对象的实现细节。侧重于对对象访问的控制和管理。结构环境类持有策略类的引用，并通过调用策略类的接口来执行具体算法。策略类之间相互独立，没有直接的关联关系。代理类和真实类都实现了相同的抽象主题接口。代理类内部持有真实类的引用，并在适当的时候调用真实类的方法，代理类与真实类之间存在明确的代理关系。功能重点提供多种算法的选择和切换，以满足不同情况下的业务需求。关注的是算法的实现和选择逻辑。控制对象的访问过程，可能在访问真实对象之前或之后进行一些预处理或后处理操作（如权限检查、资源加载等）。关注的是对象访问的控制和增强。典型示例排序算法切换（冒泡、快速、归并）。远程调用代理、图片懒加载代理、权限控制代理。总结策略模式和代理模式都是非常有用的设计模式，它们在不同的场景下发挥着重要作用。策略模式通过算法的封装和切换，提高了系统的灵活性和可扩展性，使得系统能够适应不同的业务规则和需求变化。代理模式则通过对对象访问的控制和代理，增强了系统的安全性、性能和可维护性，为系统提供了更好的管理和保护机制。在实际的软件开发中，根据具体的业务需求和场景特点，合理选择和运用这两种设计模式，可以有效地提高软件系统的质量和开发效率，构建出更加健壮和灵活的软件架构。