
1. 从“八股文”到“内功心法”为什么我们绕不开设计模式每次面试看到“设计模式”这四个字是不是感觉头都大了尤其是当它和“Java八股文”、“面试必备”这些词捆绑在一起时很多人下意识地把它归类为“背了就忘”的理论知识。但作为一个写了十几年Java的老兵我必须得说这种看法真的有点冤枉它了。设计模式从来就不是为了面试而生的它更像是软件开发领域的“内功心法”是无数前辈在解决复杂软件设计问题时总结出来的一套高效、优雅的“招式套路”。想想看你接手一个老项目看到满屏的if-else和动辄上千行的“上帝类”是不是感觉无从下手改一行代码都心惊胆战或者当你自己设计一个新模块时总觉得代码结构别扭扩展起来特别费劲加个新功能就要把老代码翻个底朝天这些问题恰恰就是设计模式要帮你解决的。它提供的不是具体的代码片段让你去“抄”而是一种思考问题、组织代码的“模式化”思维。掌握了这种思维你就能在纷繁复杂的业务需求中快速识别出问题的本质并选用最合适的“套路”来构建出灵活、健壮、易于维护的代码结构。今天我们不谈空泛的理论也不搞面试突击。我们就从最接地气的角度把这23种设计模式掰开了、揉碎了结合最具体的Java代码实现来看看它们到底是怎么在实战中发挥威力的。我会把重点放在“为什么用”和“怎么用”上分享一些我踩过的坑和总结出来的最佳实践。上半部分我们先搞定创建型和结构型这两大类共11种模式它们主要解决的是“对象怎么来”和“对象之间怎么搭”的问题。2. 创建型模式告别“new”出来的混乱让对象诞生更优雅创建型模式顾名思义关注的是对象的创建过程。在Java里我们最熟悉的就是new关键字。但无脑地new往往是代码僵化、耦合度高的开始。创建型模式的核心思想就是把对象的创建和使用分离让你在需要对象时不用关心它复杂的构造细节从而获得更大的灵活性。2.1 单例模式全局唯一的“掌门人”单例恐怕是面试中被问得最多在实际项目里也用得最广的模式之一了。它的意图非常明确保证一个类只有一个实例并提供一个全局访问点。为什么需要单例想象一下你的应用里有一个配置管理器ConfigManager或者一个线程池ThreadPool。如果每个用到的地方都new一个自己的实例那么内存中就会存在多份配置数据线程池也无法统一管理任务这显然会引发数据不一致和资源浪费。单例模式就是为了解决这类“只需要一个”的场景。Java实现与坑点实现一个线程安全的单例远不是加个static变量那么简单。下面是最经典的双重检查锁实现也是我推荐在生产环境中使用的方式public class Singleton { // volatile 关键字防止指令重排确保 instance 的初始化完成对其他线程可见 private static volatile Singleton instance; // 私有化构造器堵死外部 new 的路 private Singleton() { // 防止通过反射破坏单例 if (instance ! null) { throw new RuntimeException(Use getInstance() method to get the single instance.); } } public static Singleton getInstance() { // 第一次检查避免不必要的同步 if (instance null) { synchronized (Singleton.class) { // 第二次检查确保只有一个线程能创建实例 if (instance null) { instance new Singleton(); } } } return instance; } }注意这里有个关键点volatile。没有它在超高并发下由于JVM的指令重排序优化其他线程可能会拿到一个未初始化完全的对象半初始化状态导致程序出错。这是单例模式一个非常经典的坑。更优雅的写法枚举单例。从Java 5开始利用枚举实现单例被公认为是最佳实践它天生防反射、防序列化破坏且写法简洁。public enum EnumSingleton { INSTANCE; public void doSomething() { System.out.println(Doing something...); } } // 使用EnumSingleton.INSTANCE.doSomething();实战心得单例虽好但不能滥用。它本质上是一个全局状态会带来隐式的耦合不利于单元测试因为状态无法隔离。在Spring这类IoC容器管理的项目中通常将Bean的作用域设为singleton由容器来管理单例生命周期这比手写单例更规范、更强大。2.2 工厂方法模式把“生孩子”的权利下放当你发现代码里有一堆根据类型进行判断的if-else或switch并且都在创建对象时就该考虑工厂方法模式了。它定义了一个创建对象的接口但让子类决定实例化哪一个类。场景还原假设我们有一个日志系统需要支持输出到文件、数据库和控制台。// 反例硬编码的创建逻辑 public Logger createLogger(String type) { if (file.equals(type)) { return new FileLogger(); } else if (db.equals(type)) { return new DatabaseLogger(); } else if (console.equals(type)) { return new ConsoleLogger(); } throw new IllegalArgumentException(Unsupported logger type); }这段代码的问题在于每增加一种日志类型就要修改这个createLogger方法违反了开闭原则。工厂方法改造// 1. 产品接口 public interface Logger { void log(String message); } // 2. 具体产品 public class FileLogger implements Logger { /* ... */ } public class DatabaseLogger implements Logger { /* ... */ } public class ConsoleLogger implements Logger { /* ... */ } // 3. 创建者抽象类核心 public abstract class LoggerFactory { // 这就是“工厂方法” public abstract Logger createLogger(); // 可以包含一些与产品相关的公共操作 public void writeLog(String message) { Logger logger this.createLogger(); // 调用工厂方法 logger.log(message); } } // 4. 具体创建者 public class FileLoggerFactory extends LoggerFactory { Override public Logger createLogger() { // 可以在这里进行复杂的初始化比如设置文件路径 return new FileLogger(); } } public class DatabaseLoggerFactory extends LoggerFactory { /* ... */ } // 使用 LoggerFactory factory new FileLoggerFactory(); Logger logger factory.createLogger(); logger.log(Hello Factory Method!);核心价值使用方客户端完全不需要知道FileLogger具体是怎么创建的它只和LoggerFactory以及Logger接口打交道。新增一个NetworkLogger你只需要新增NetworkLogger和NetworkLoggerFactory原有代码一行都不用改。系统的可扩展性得到了质的提升。2.3 抽象工厂模式创建“产品家族”工厂方法模式针对的是一个产品等级结构比如各种Logger。而抽象工厂模式是针对多个产品等级结构它提供一个创建一系列相关或相互依赖对象的接口而无需指定它们具体的类。简单说就是创建“一套”产品。经典场景GUI库。一个GUI库需要提供一套风格一致的组件比如“暗黑主题”的按钮、文本框、下拉框“明亮主题”的按钮、文本框、下拉框。// 1. 抽象产品族按钮和文本框 public interface Button { void render(); } public interface TextField { void input(); } // 2. 具体产品族暗黑系列 public class DarkButton implements Button { Override public void render() { System.out.println(渲染一个暗黑风格按钮); } } public class DarkTextField implements TextField { Override public void input() { System.out.println(暗黑风格文本框接收输入); } } // 3. 具体产品族明亮系列 public class LightButton implements Button { /* ... */ } public class LightTextField implements TextField { /* ... */ } // 4. 抽象工厂核心 public interface GUIFactory { Button createButton(); TextField createTextField(); } // 5. 具体工厂 public class DarkThemeFactory implements GUIFactory { Override public Button createButton() { return new DarkButton(); } Override public TextField createTextField() { return new DarkTextField(); } } public class LightThemeFactory implements GUIFactory { /* ... */ } // 客户端使用 public class Application { private Button button; private TextField textField; public Application(GUIFactory factory) { // 依赖抽象工厂 this.button factory.createButton(); this.textField factory.createTextField(); } public void renderUI() { button.render(); textField.input(); } public static void main(String[] args) { // 只需切换工厂即可切换整套UI风格 GUIFactory factory new DarkThemeFactory(); // 或 new LightThemeFactory() Application app new Application(factory); app.renderUI(); } }与工厂方法的区别工厂方法模式一般只生产一个产品抽象工厂模式生产一个产品家族。抽象工厂的接口里通常有多个工厂方法。在Java的JDBC API中Connection就是一个抽象工厂它可以创建Statement、PreparedStatement、CallableStatement这一系列相关的“数据库操作产品”。踩坑提醒抽象工厂模式虽然强大但它的缺点也很明显难以支持新种类的产品。比如如果要在GUI家族里新增一个Checkbox产品那么需要修改GUIFactory接口以及所有具体工厂类。因此在产品族结构相对稳定但需要经常切换整个族系的场景下它才大放异彩。2.4 建造者模式告别“伸缩构造器”噩梦你有没有写过或者见过这样的构造函数public class NutritionFacts { private final int servingSize; // (mL) required private final int servings; // (per container) required private final int calories; // optional private final int fat; // (g) optional private final int sodium; // (mg) optional private final int carbohydrate; // (g) optional public NutritionFacts(int servingSize, int servings) { this(servingSize, servings, 0); } public NutritionFacts(int servingSize, int servings, int calories) { this(servingSize, servings, calories, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat) { this(servingSize, servings, calories, fat, 0); } // ... 更多重载构造函数 }或者更糟使用一个全参构造器但很多参数你根本用不上调用时不得不传一堆0或nullnew NutritionFacts(240, 8, 100, 0, 35, 27); // fat和sodium是0但你必须传这就是所谓的“伸缩构造器”模式它让代码难以编写更难以阅读——你根本记不住第5个参数代表什么。建造者模式登场它通过一个独立的建造者对象一步步构造一个复杂对象最后通过一个build()方法返回最终产品。特别适用于那些拥有大量可选参数或者构造过程复杂的类。public class NutritionFacts { private final int servingSize; private final int servings; private final int calories; private final int fat; private final int sodium; private final int carbohydrate; // 私有构造器只能通过Builder构建 private NutritionFacts(Builder builder) { servingSize builder.servingSize; servings builder.servings; calories builder.calories; fat builder.fat; sodium builder.sodium; carbohydrate builder.carbohydrate; } // 静态内部类 Builder public static class Builder { // 必需参数 private final int servingSize; private final int servings; // 可选参数 - 初始化默认值 private int calories 0; private int fat 0; private int sodium 0; private int carbohydrate 0; public Builder(int servingSize, int servings) { this.servingSize servingSize; this.servings servings; } public Builder calories(int val) { calories val; return this; // 返回this支持链式调用 } public Builder fat(int val) { fat val; return this; } public Builder sodium(int val) { sodium val; return this; } public Builder carbohydrate(int val) { carbohydrate val; return this; } public NutritionFacts build() { // 可以在这里进行参数校验 if (servingSize 0 || servings 0) { throw new IllegalArgumentException(Serving size and servings must be positive); } return new NutritionFacts(this); } } } // 客户端使用清晰、灵活、可读性极强 NutritionFacts cocaCola new NutritionFacts.Builder(240, 8) .calories(100) .sodium(35) .carbohydrate(27) .build();优势一览代码可读性极高链式调用让参数意义一目了然。参数可选灵活只设置你关心的参数。保证对象不可变所有字段都是final的通过构造器一次性设置线程安全。可进行构造参数校验在build()方法里统一校验比在多个构造器里分散校验更可靠。实战扩展在创建一些复杂DTO数据传输对象或配置对象时建造者模式几乎是标配。很多开源库如OkHttp、Retrofit的客户端配置都大量使用了建造者模式。结合Lombok库的Builder注解可以让你免去手写Builder类的繁琐。2.5 原型模式复制粘贴的艺术有时候创建一个新对象的成本很高比如需要从数据库加载大量数据或进行复杂的计算初始化而你又需要多个相似的对象。这时候原型模式就派上用场了通过复制一个现有实例原型来创建新实例而不是新建。Java中的天然支持Java提供了Cloneable接口和Object.clone()方法来实现原型模式。但这里水很深。浅拷贝与深拷贝这是原型模式最大的坑。Object.clone()默认是浅拷贝它只复制对象本身和其基本类型字段对于引用类型字段复制的是引用地址新旧对象会共享同一份引用数据。public class Sheep implements Cloneable { private String name; private Date birthday; // 引用类型 // ... 构造器、getter/setter Override protected Object clone() throws CloneNotSupportedException { return super.clone(); // 默认浅拷贝 } } Sheep dolly new Sheep(Dolly, new Date()); Sheep dollyClone (Sheep) dolly.clone(); System.out.println(dolly dollyClone); // false是两个对象 System.out.println(dolly.getBirthday() dollyClone.getBirthday()); // true生日对象是同一个修改dollyClone的birthdaydolly的生日也会变这通常不是我们想要的。实现深拷贝手动逐层克隆在clone()方法里对每个引用字段也调用其clone()方法如果它也支持。Override protected Object clone() throws CloneNotSupportedException { Sheep cloned (Sheep) super.clone(); cloned.birthday (Date) this.birthday.clone(); // 对Date也进行克隆 return cloned; }通过序列化实现将对象写入字节流再从字节流读出来。这种方式能实现彻底的深拷贝但要求所有涉及的对象都实现Serializable接口且性能开销较大。import java.io.*; public class DeepCopyUtil { SuppressWarnings(unchecked) public static T extends Serializable T deepCopy(T obj) { try (ByteArrayOutputStream bos new ByteArrayOutputStream(); ObjectOutputStream oos new ObjectOutputStream(bos)) { oos.writeObject(obj); oos.flush(); try (ByteArrayInputStream bis new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois new ObjectInputStream(bis)) { return (T) ois.readObject(); } } catch (IOException | ClassNotFoundException e) { throw new RuntimeException(Deep copy failed, e); } } }使用场景原型模式在需要快速创建大量相似对象且构造成本较高的场景下非常有用比如游戏中的子弹、敌人或者从模板生成文档。但在Java中由于深拷贝的实现复杂度它的使用频率不如前几种创建型模式高。很多时候我们更倾向于使用工厂方法配合缓存如池化技术来达到类似目的。3. 结构型模式搭建灵活稳固的代码“骨架”创建型模式解决了对象“怎么来”的问题结构型模式则关注对象“怎么组合”如何通过组合类或对象形成更大、更复杂的结构同时保持结构的灵活和高效。它们就像建筑中的连接件让独立的模块能够协同工作。3.1 适配器模式让不兼容的接口一起工作这可能是最常用、最直观的结构型模式了。生活中电源适配器让美标插头能在中国的插座上使用。代码里适配器模式让原本接口不兼容的类可以合作。两种实现方式类适配器通过继承适配器继承自被适配者并实现目标接口。// 目标接口中国插座 public interface ChinaSocket { void useWithChinaPlug(); } // 被适配者美国插头 public class AmericanPlug { public void useWithAmericanSocket() { System.out.println(使用美标插头供电); } } // 适配器 public class PlugAdapter extends AmericanPlug implements ChinaSocket { Override public void useWithChinaPlug() { System.out.println(适配器转换中...); super.useWithAmericanSocket(); // 调用父类方法 System.out.println(成功为中国设备供电); } }这种方式不够灵活因为Java是单继承适配器一旦继承了AmericanPlug就无法再继承其他类。对象适配器通过组合推荐适配器持有被适配者的一个实例。public class PlugAdapter implements ChinaSocket { private AmericanPlug americanPlug; // 组合 public PlugAdapter(AmericanPlug americanPlug) { this.americanPlug americanPlug; } Override public void useWithChinaPlug() { System.out.println(适配器转换中...); americanPlug.useWithAmericanSocket(); // 委托给被适配对象 System.out.println(成功为中国设备供电); } } // 使用 AmericanPlug usPlug new AmericanPlug(); ChinaSocket socketInChina new PlugAdapter(usPlug); socketInChina.useWithChinaPlug();对象适配器更符合“组合优于继承”的原则更灵活可以适配一个类的任何子类。实战场景在Java中InputStreamReader和OutputStreamWriter就是经典的适配器它们将字节流InputStream/OutputStream适配成了字符流Reader/Writer。在集成老系统、使用第三方库时当对方的API不符合你的接口规范适配器就是你的救星。3.2 桥接模式分离抽象与实现让它们独立变化桥接模式理解起来有点抽象但它的思想非常强大将抽象部分与它的实现部分分离使它们都可以独立地变化。这里的“实现”不是指“实现类”而是指“底层实现维度”。一个典型的坏味道类爆炸。比如要设计一个图形库支持多种形状圆形、方形和多种颜色红色、蓝色。如果用继承来实现Shape / \ Circle Square / \ / \ RedCircle BlueCircle RedSquare BlueSquare每增加一种颜色或形状类的数量就会成倍增长。这就是将“形状”和“颜色”这两个维度耦合在了一起。桥接模式解法将“形状”抽象和“颜色”实现分离通过组合的方式连接。// 1. 实现化角色颜色 public interface Color { String applyColor(); } public class Red implements Color { Override public String applyColor() { return 红色; } } public class Blue implements Color { /* ... */ } // 2. 抽象化角色形状 public abstract class Shape { protected Color color; // 组合一个颜色对象桥接的关键 public Shape(Color color) { this.color color; } public abstract void draw(); } // 3. 修正抽象化角色具体形状 public class Circle extends Shape { public Circle(Color color) { super(color); } Override public void draw() { System.out.println(绘制一个 color.applyColor() 的圆形); } } public class Square extends Shape { /* ... */ } // 使用 Color red new Red(); Shape redCircle new Circle(red); redCircle.draw(); // 输出绘制一个红色的圆形 Shape blueSquare new Square(new Blue()); blueSquare.draw();核心优势现在形状和颜色可以独立扩展。要加一个绿色只需新增Green类形状类无需改动。要加一个三角形只需新增Triangle类颜色类也无需改动。彻底避免了类爆炸。桥接 vs. 适配器很多人容易混淆。适配器是“事后补救”用于连接两个已有但不兼容的接口。桥接是“事前设计”用于在抽象和实现之间建立一个稳定的连接以便两者可以独立演化。桥接模式通常用在设计初期是系统架构的一部分。3.3 组合模式处理树形结构的统一之道当你需要表示“部分-整体”的层次结构并且希望用户以统一的方式对待单个对象和组合对象时就用组合模式。文件系统是教科书级的例子文件和文件夹都是“条目”文件夹里可以包含文件或其他文件夹。// 1. 组件接口统一了叶子文件和容器文件夹的行为 public interface FileSystemComponent { void showDetails(String indent); long getSize(); } // 2. 叶子节点文件 public class File implements FileSystemComponent { private String name; private long size; public File(String name, long size) { this.name name; this.size size; } Override public void showDetails(String indent) { System.out.println(indent - 文件: name ( size bytes)); } Override public long getSize() { return size; } } // 3. 容器节点文件夹 public class Directory implements FileSystemComponent { private String name; private ListFileSystemComponent children new ArrayList(); public Directory(String name) { this.name name; } public void addComponent(FileSystemComponent component) { children.add(component); } public void removeComponent(FileSystemComponent component) { children.remove(component); } Override public void showDetails(String indent) { System.out.println(indent 文件夹: name); for (FileSystemComponent child : children) { child.showDetails(indent ); // 递归调用 } } Override public long getSize() { long totalSize 0; for (FileSystemComponent child : children) { totalSize child.getSize(); // 递归计算 } return totalSize; } } // 使用 Directory root new Directory(根目录); Directory docs new Directory(文档); File readme new File(README.txt, 1024); File notes new File(notes.md, 2048); docs.addComponent(readme); docs.addComponent(notes); root.addComponent(docs); root.showDetails(); System.out.println(根目录总大小: root.getSize() bytes);模式要点组合模式的核心在于Directory容器和File叶子都实现了同一个接口FileSystemComponent。对于客户端来说操作一个文件和操作一个文件夹里面可能包含无数文件和子文件夹的API是完全一样的showDetails,getSize。这使得处理递归结构变得异常简单和统一。应用场景除了文件系统GUI中的容器和组件如Swing中的JPanel和JButton、公司组织架构部门与员工、菜单与子菜单等凡是呈现树形结构且需要统一操作的地方都可以考虑组合模式。3.4 装饰器模式动态添加功能比继承更灵活想象一下你要为一杯咖啡添加配料加糖、加奶、加摩卡。如果使用继承Coffee / \ SugarCoffee MilkCoffee / \ / \ SugarMilkCoffee ... 类爆炸同样会陷入类爆炸的困境。装饰器模式提供了一种更优雅的解决方案动态地给一个对象添加一些额外的职责。它比生成子类更为灵活。Java I/O库是装饰器模式的典范// 我们想读取一个文件并缓存其内容还要能按行读取。 // 不使用装饰器 // FileInputStream - BufferedInputStream - InputStreamReader - BufferedReader // 每一步都“装饰”了前一步增加了新功能。 // 模拟一个简单的装饰器例子 // 1. 抽象组件 public interface Beverage { String getDescription(); double cost(); } // 2. 具体组件 public class Espresso implements Beverage { Override public String getDescription() { return 浓缩咖啡; } Override public double cost() { return 1.99; } } // 3. 抽象装饰器关键它实现了组件接口并持有一个组件引用 public abstract class CondimentDecorator implements Beverage { protected Beverage beverage; // 被装饰的对象 public CondimentDecorator(Beverage beverage) { this.beverage beverage; } // 不实现 getDescription 和 cost留给具体装饰器 } // 4. 具体装饰器 public class Milk extends CondimentDecorator { public Milk(Beverage beverage) { super(beverage); } Override public String getDescription() { return beverage.getDescription() , 加奶; } Override public double cost() { return beverage.cost() 0.20; // 在原有价格上加钱 } } public class Mocha extends CondimentDecorator { /* 类似实现加价0.30 */ } // 使用可以任意组合装饰且顺序灵活 Beverage drink new Espresso(); System.out.println(drink.getDescription() drink.cost()); drink new Milk(drink); // 用Milk装饰Espresso System.out.println(drink.getDescription() drink.cost()); drink new Mocha(drink); // 再用Mocha装饰现在是Mocha(Milk(Espresso)) System.out.println(drink.getDescription() drink.cost());输出浓缩咖啡 1.99 浓缩咖啡, 加奶 2.19 浓缩咖啡, 加奶, 加摩卡 2.49核心思想装饰器类和被装饰的组件实现相同的接口。装饰器内部持有一个组件对象的引用并在调用组件方法的前后添加自己的行为。这样你可以通过嵌套多个装饰器来动态地、透明地对客户端而言为对象叠加任意多的功能。与继承对比继承是静态的在编译时就确定了功能组合。装饰是动态的可以在运行时任意组合提供了巨大的灵活性。Java I/O流、Servlet API中的HttpServletRequestWrapper都是装饰器模式的经典应用。3.5 外观模式提供统一的“服务窗口”当一个系统内部非常复杂由多个子系统组成时客户端要使用这个系统可能需要了解每个子系统的接口并进行复杂的调用。外观模式就是为这些复杂的子系统提供一个统一的、更简洁的高级接口让客户端更容易使用。生活例子你去电脑城组装一台电脑不需要自己去找CPU、内存、硬盘、显卡的供应商分别购买、测试兼容性。你只需要找到一家装机店外观告诉老板你的需求和预算老板会协调所有子系统硬件为你组装好一台整机。代码示例// 复杂的子系统类 public class CPU { public void start() { System.out.println(CPU启动); } public void execute() { System.out.println(CPU执行指令); } } public class Memory { public void load() { System.out.println(内存加载数据); } } public class HardDrive { public void read() { System.out.println(硬盘读取数据); } } // 外观类电脑 public class Computer { private CPU cpu; private Memory memory; private HardDrive hardDrive; public Computer() { this.cpu new CPU(); this.memory new Memory(); this.hardDrive new HardDrive(); } // 对外提供的简化接口 public void startComputer() { System.out.println( 开始启动电脑 ); cpu.start(); memory.load(); hardDrive.read(); cpu.execute(); System.out.println( 电脑启动完成 \n); } public void shutdownComputer() { System.out.println(电脑关机中...); // ... 调用各个子系统的关闭方法 } } // 客户端 public class Client { public static void main(String[] args) { Computer myPC new Computer(); myPC.startComputer(); // 客户端只需要调用一个简单的方法 // ... 使用电脑 myPC.shutdownComputer(); } }作用简化接口客户端无需了解子系统内部的复杂关系。解耦将客户端与复杂的子系统解耦。即使子系统内部发生改变只要外观接口不变客户端代码就无需修改。易于使用降低了学习成本和使用门槛。注意外观模式并不禁止客户端直接访问子系统。它只是提供了一个更方便的入口。在架构设计中我们常说的“服务层”、“门面层”就是外观模式思想的体现。3.6 享元模式共享细粒度对象节省内存享元模式的核心是共享用于减少创建对象的数量以减少内存占用和提高性能。它要求将对象的**内部状态Intrinsic State和外部状态Extrinsic State**分离。内部状态是可以共享的、不变的部分而外部状态是随环境变化、不可共享的部分由客户端在使用时传入。经典案例文本编辑器中的字符对象。一篇文章可能有成千上万个字符但字母表只有26个大小写52个。如果每个字符都创建一个独立的对象内存消耗巨大。// 1. 享元接口 public interface Character { void display(String font, int size, int colorRGB); // 外部状态作为参数传入 } // 2. 具体享元包含内部状态 public class ConcreteCharacter implements Character { private final char symbol; // 内部状态字符本身不可变可共享 public ConcreteCharacter(char symbol) { this.symbol symbol; } Override public void display(String font, int size, int colorRGB) { // 外部状态字体、大小、颜色 System.out.println(字符: symbol , 字体: font , 大小: size , 颜色: # Integer.toHexString(colorRGB)); } } // 3. 享元工厂负责创建和管理享元对象 public class CharacterFactory { private static final MapCharacter, ConcreteCharacter pool new HashMap(); public static Character getCharacter(char symbol) { // 如果池中已有直接返回 ConcreteCharacter charObj pool.get(symbol); if (charObj null) { // 否则创建新的放入池中 charObj new ConcreteCharacter(symbol); pool.put(symbol, charObj); System.out.println(创建新字符对象: symbol); } return charObj; } } // 客户端使用 public class TextEditor { public static void main(String[] args) { String text Hello, Flyweight!; ListCharacter characters new ArrayList(); // 模拟渲染文本外部状态随机生成 Random rand new Random(); for (char c : text.toCharArray()) { Character charObj CharacterFactory.getCharacter(c); // 获取享元对象 // 传入外部状态 charObj.display(Arial, 12 rand.nextInt(5), rand.nextInt(0xFFFFFF)); characters.add(charObj); } System.out.println(\n总共处理了 text.length() 个字符。); System.out.println(享元池中实际只有 CharacterFactory.getPoolSize() 个字符对象。); } }输出会显示像‘H’, ‘e’, ‘l’, ‘o’这样的重复字符只被创建了一次。应用场景享元模式的有效性很大程度上取决于环境。它适用于系统中存在大量相似对象。这些对象的大部分状态可以外部化变为外部状态。使用享元后能显著减少内存消耗。 Java中的String常量池、Integer等包装类的缓存Integer.valueOf(int)会缓存-128~127都是享元模式的思想体现。在游戏开发中渲染大量相同类型的树木、士兵时享元模式能极大优化性能。3.7 代理模式对象的“替身”与“中介”代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。客户端通过代理对象间接访问真实对象代理可以在访问前后添加一些额外的逻辑。几种常见的代理类型远程代理为一个位于不同地址空间的对象提供本地代表。例如RPC框架的Stub。虚拟代理用于创建开销很大的对象。例如网页中的图片懒加载先用一个占位图代理显示后台加载真实大图。保护代理控制对原始对象的访问权限。智能引用代理在访问对象时执行一些附加操作如引用计数、懒加载、日志记录等。静态代理示例以日志代理为例// 1. 抽象主题 public interface UserService { void addUser(String name); } // 2. 真实主题 public class UserServiceImpl implements UserService { Override public void addUser(String name) { System.out.println(添加用户: name); // ... 实际的数据库操作 } } // 3. 代理类静态代理 public class UserServiceProxy implements UserService { private UserService realService; // 持有真实对象的引用 public UserServiceProxy(UserService realService) { this.realService realService; } Override public void addUser(String name) { long start System.currentTimeMillis(); System.out.println(【代理】开始执行 addUser...); // 调用真实对象的方法 realService.addUser(name); long end System.currentTimeMillis(); System.out.println(【代理】addUser 执行完毕耗时 (end - start) ms); } } // 使用 UserService realService new UserServiceImpl(); UserService proxy new UserServiceProxy(realService); proxy.addUser(张三);静态代理的缺点很明显每个真实类都需要一个对应的代理类如果方法很多代理类会非常臃肿。动态代理Java内置更强大Java的java.lang.reflect.Proxy类可以在运行时动态创建代理。import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; // 1. 调用处理器 public class LoggingInvocationHandler implements InvocationHandler { private final Object target; // 被代理的真实对象 public LoggingInvocationHandler(Object target) { this.target target; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long start System.currentTimeMillis(); System.out.println(【动态代理】开始调用方法: method.getName()); // 调用真实对象的方法 Object result method.invoke(target, args); long end System.currentTimeMillis(); System.out.println(【动态代理】方法 method.getName() 调用完毕耗时 (end - start) ms); return result; } } // 使用 UserService realService new UserServiceImpl(); InvocationHandler handler new LoggingInvocationHandler(realService); // 动态创建代理对象 UserService dynamicProxy (UserService) Proxy.newProxyInstance( UserService.class.getClassLoader(), // 类加载器 new Class[]{UserService.class}, // 代理要实现的接口列表 handler // 调用处理器 ); dynamicProxy.addUser(李四);动态代理的优势在于一个InvocationHandler可以代理任意接口的任意方法通用性极强。Spring AOP面向切面编程的核心就是基于动态代理以及CGLIB字节码增强实现的用于无侵入地添加日志、事务、安全等“横切关注点”功能。代理模式与装饰器模式的区别两者结构相似但目的不同。代理模式重在控制访问可能限制或增强访问代理和真实对象的关系通常在编译时或运行时动态代理就确定了。装饰器模式重在动态添加功能装饰器是透明叠加的客户端可以任意组合装饰顺序且装饰器和组件通常实现相同的完整接口。简单说代理是对象的“经纪人”控制你见不见他装饰是对象的“衣服”给他添加功能。