Skip to content

设计模式

软件设计中的设计模式是解决特定场景下常见设计问题的、可复用的、经过验证的最佳实践方案。它们不是可以直接转化为代码的完整解决方案,而是提供了设计蓝图和指导原则,帮助你构建更灵活、可维护、可扩展和可复用的软件。

最著名和基础的设计模式分类来源于“四人帮” 的经典著作《设计模式:可复用面向对象软件的基础》,其中描述了 23 种核心模式,通常分为三大类:创建型模式、结构型模式和行为型模式。

1. 创建型模式

关注对象的创建机制,旨在以更灵活、更符合约束的方式创建对象,而不是直接使用 new 操作符。

1.1 单例模式 (Singleton Pattern)

1.1.1 为什么需要单例模式?

当系统中需要确保一个类只有一个实例(如配置管理器、线程池),避免资源冲突或重复创建时使用。核心是控制实例数量,节省内存资源。

1.1.2 核心原理

  1. 私有化构造函数(禁止外部new
  2. 静态私有成员变量保存唯一实例
  3. 静态公有方法提供全局访问点

1.1.3 最佳实践场景

  • 数据库连接池
  • 日志记录器
  • 应用配置对象
  • 设备管理器(如打印机)

1.1.4 java代码示例

java
public class Singleton {
    private static volatile Singleton instance; // volatile防止指令重排

    private Singleton() {} // 私有构造

    public static Singleton getInstance() {
        if (instance == null) { // 双重检查锁
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

1.2 工厂方法模式 (Factory Method Pattern)

1.2.1 为什么需要工厂方法模式?

当创建对象需要复杂初始化逻辑,或希望将对象创建与使用解耦时使用。避免客户端直接依赖具体类。

1.2.2 核心原理

  1. 定义抽象工厂接口声明创建方法
  2. 每个具体产品对应一个具体工厂
  3. 通过工厂子类决定实例化的具体类

1.2.3 最佳实践场景

  • 日志记录器(文件/数据库日志)
  • 支付系统(支付宝/微信支付)
  • 跨平台UI组件创建

1.2.4 java代码示例

java
// 1. 产品接口
interface Vehicle {
    void drive();
}

// 2. 具体产品
class Car implements Vehicle {
    @Override
    public void drive() {
        System.out.println("Driving a car");
    }
}

// 3. 抽象工厂
interface VehicleFactory {
    Vehicle createVehicle();
}

// 4. 具体工厂
class CarFactory implements VehicleFactory {
    @Override
    public Vehicle createVehicle() {
        return new Car();
    }
}

// 客户端使用
public class Client {
    public static void main(String[] args) {
        VehicleFactory factory = new CarFactory();
        Vehicle vehicle = factory.createVehicle();
        vehicle.drive(); // 输出: Driving a car
    }
}

1.3 抽象工厂模式

1.3.1 为什么需要

当需要创建一组相关或依赖对象(如整套UI组件),并确保产品兼容性时使用。扩展工厂方法模式到产品族。

1.3.2 核心原理

  1. 抽象工厂声明多个创建方法
  2. 每个具体工厂生产同一产品族的不同产品
  3. 客户端通过抽象接口操作产品

1.3.3 最佳实践场景

  • 跨平台GUI库(Windows/Mac按钮+文本框)
  • 数据库访问套件(连接+命令+读取器)
  • 游戏角色装备系统(武器+防具)

1.3.4 java代码示例

java
// 1. 抽象产品族
interface Button {
    void render();
}
interface TextField {
    void input();
}

// 2. 具体产品 (Windows系列)
class WinButton implements Button {
    @Override
    public void render() {
        System.out.println("Windows风格按钮");
    }
}
class WinTextField implements TextField {
    @Override
    public void input() {
        System.out.println("Windows文本框输入");
    }
}

// 3. 抽象工厂
interface GUIFactory {
    Button createButton();
    TextField createTextField();
}

// 4. 具体工厂
class WinFactory implements GUIFactory {
    @Override
    public Button createButton() {
        return new WinButton();
    }
    @Override
    public TextField createTextField() {
        return new WinTextField();
    }
}

// 客户端使用
public class Application {
    private final Button button;
    private final TextField textField;

    public Application(GUIFactory factory) {
        button = factory.createButton();
        textField = factory.createTextField();
    }

    public void run() {
        button.render();
        textField.input();
    }
}

1.4 建造者模式

1.4.1 为什么需要

对象构造过程复杂(多参数/分步骤),需要灵活创建不同表示时使用。解决构造函数参数过多问题。

1.4.2 核心原理

  1. 将对象构建拆分为多个步骤
  2. 指挥者(Director)控制构建流程
  3. 建造者(Builder)实现具体构建
  4. 产品与构建过程分离

1.4.3 最佳实践场景

  • 生成复杂报表(标题/表头/数据体)
  • 创建HTML文档(head/body元素)
  • 快餐套餐定制(主食+饮料+甜点)

1.4.4 java代码示例

java
// 1. 产品
class Computer {
    private String cpu;
    private String ram;
    private String ssd;

    public void setCpu(String cpu) { this.cpu = cpu; }
    public void setRam(String ram) { this.ram = ram; }
    public void setSsd(String ssd) { this.ssd = ssd; }
}

// 2. 抽象建造者
interface ComputerBuilder {
    void buildCpu();
    void buildRam();
    void buildSsd();
    Computer getResult();
}

// 3. 具体建造者
class GamingComputerBuilder implements ComputerBuilder {
    private Computer computer = new Computer();

    @Override
    public void buildCpu() {
        computer.setCpu("Intel i9");
    }
    @Override
    public void buildRam() {
        computer.setRam("32GB DDR5");
    }
    @Override
    public void buildSsd() {
        computer.setSsd("2TB NVMe");
    }
    @Override
    public Computer getResult() {
        return computer;
    }
}

// 4. 指挥者
class Director {
    public Computer construct(ComputerBuilder builder) {
        builder.buildCpu();
        builder.buildRam();
        builder.buildSsd();
        return builder.getResult();
    }
}

// 客户端使用
public class Client {
    public static void main(String[] args) {
        Director director = new Director();
        ComputerBuilder builder = new GamingComputerBuilder();
        Computer computer = director.construct(builder);
    }
}

1.5 原型模式

1.5.1 为什么需要

对象创建成本高(如数据库查询),或需要动态配置对象状态时使用。通过克隆避免重复初始化

1.5.2 核心原理

  1. 实现Cloneable接口
  2. 重写clone()方法
  3. 深拷贝与浅拷贝控制

1.5.3 最佳实践场景

  • 游戏场景中大量相似NPC创建
  • 撤销/重做功能的状态保存
  • 复杂配置对象的快速复制

1.5.4 java代码示例

java
class Prototype implements Cloneable {
    private String data;
    private List<String> list = new ArrayList<>();

    public void loadData() {
        // 模拟耗时操作(如数据库查询)
        this.data = "核心数据";
        list.add("A");
        list.add("B");
    }

    // 深拷贝实现
    @Override
    public Prototype clone() {
        try {
            Prototype clone = (Prototype) super.clone();
            clone.list = new ArrayList<>(this.list); // 深拷贝可变对象
            return clone;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

// 客户端使用
public class Client {
    public static void main(String[] args) {
        Prototype original = new Prototype();
        original.loadData(); // 耗时操作

        Prototype copy = original.clone(); // 快速复制
    }
}

2. 结构型模式

关注如何组合类和对象以形成更大的结构,主要处理对象之间的组合关系。

2.1 适配器模式

2.1.1 为什么需要适配器模式?

当我们需要使用一个已经存在的类,但是它的接口不符合我们的需求时,适配器模式可以帮助我们。它允许原本接口不兼容的类能够一起工作。例如,我们有一个旧的日志系统,现在要使用新的日志接口,我们可以创建一个适配器来让旧的日志系统适配新的接口。

2.1.2 核心原理

适配器模式通过将一个类的接口转换成客户期望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。它主要有两种形式:类适配器(通过继承)和对象适配器(通过组合)。对象适配器更常用,因为它更灵活(使用组合,可以适配多个源)。

2.1.3 最佳实践场景

  • 使用第三方库或遗留代码,需要与现有系统集成。
  • 系统需要使用多个现有子类,但每个子类的接口不同,需要一个统一的接口。
  • 当需要创建一个可以复用的类,该类与不相关或不可预见的类(即那些接口可能不一定兼容的类)协同工作。

2.1.4 java代码示例

假设我们有一个旧的LegacyLogger类,它有一个方法logMessage(String message),而我们现在希望使用新的Logger接口,该接口有log(String msg)方法。

java
// 目标接口(新接口)
interface Logger {
    void log(String msg);
}

// 旧的日志类
class LegacyLogger {
    public void logMessage(String message) {
        System.out.println("Legacy Logger: " + message);
    }
}

// 适配器:使用组合(对象适配器)
class LoggerAdapter implements Logger {
    private LegacyLogger legacyLogger;

    public LoggerAdapter(LegacyLogger legacyLogger) {
        this.legacyLogger = legacyLogger;
    }

    @Override
    public void log(String msg) {
        legacyLogger.logMessage(msg);
    }
}

// 客户端代码
public class AdapterDemo {
    public static void main(String[] args) {
        Logger logger = new LoggerAdapter(new LegacyLogger());
        logger.log("Hello Adapter Pattern!");
    }
}

2.2 桥接模式

2.2.1 为什么需要桥接模式?

桥接模式用于将抽象部分与实现部分分离,使它们可以独立变化。当有多个变化维度时,使用继承会导致类爆炸(例如,有多个抽象和多个实现,组合起来会有大量的子类)。桥接模式通过组合代替继承,解决这个问题。

2.2.2 核心原理

桥接模式将抽象和实现放在两个不同的类层次结构中,从而可以独立扩展。抽象层次结构中包含对实现层次结构的引用。这样,抽象和实现可以独立变化,不会相互影响。

2.2.3 最佳实践场景

  • 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
  • 避免多层继承结构,使用组合关系来替代。
  • 需要在运行时切换不同的实现。

2.2.4 java代码示例

假设我们有一个图形类(Shape),它可以有不同的绘制方式(如用不同API绘制:OpenGL、DirectX)。我们可以将图形和绘制API分离。

java
// 实现部分接口:绘图API
interface DrawingAPI {
    void drawCircle(double x, double y, double radius);
}

class OpenGLAPI implements DrawingAPI {
    @Override
    public void drawCircle(double x, double y, double radius) {
        System.out.printf("OpenGL: circle at (%.2f, %.2f) with radius %.2f\n", x, y, radius);
    }
}

class DirectXAPI implements DrawingAPI {
    @Override
    public void drawCircle(double x, double y, double radius) {
        System.out.printf("DirectX: circle at (%.2f, %.2f) with radius %.2f\n", x, y, radius);
    }
}

// 抽象部分:图形
abstract class Shape {
    protected DrawingAPI drawingAPI;

    protected Shape(DrawingAPI drawingAPI) {
        this.drawingAPI = drawingAPI;
    }

    public abstract void draw();
}

class Circle extends Shape {
    private double x, y, radius;

    public Circle(double x, double y, double radius, DrawingAPI drawingAPI) {
        super(drawingAPI);
        this.x = x;
        this.y = y;
        this.radius = radius;
    }

    @Override
    public void draw() {
        drawingAPI.drawCircle(x, y, radius);
    }
}

// 客户端代码
public class BridgeDemo {
    public static void main(String[] args) {
        Shape circle1 = new Circle(1, 2, 3, new OpenGLAPI());
        circle1.draw();

        Shape circle2 = new Circle(5, 7, 11, new DirectXAPI());
        circle2.draw();
    }
}

2.3 组合模式

2.3.1 为什么需要组合模式?

组合模式用于表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。当我们需要处理树形结构(如文件系统、菜单等)时,组合模式可以让客户端忽略单个对象和组合对象的区别

2.3.2 核心原理

组合模式通过一个统一的接口(Component)来表示叶子节点(Leaf)和组合节点(Composite),使得客户端可以一致地对待单个对象和组合对象。组合节点可以包含其他组件(可以是叶子节点,也可以是其他组合节点)。

2.3.3 最佳实践场景

  • 需要表示对象的整体与部分层次结构。
  • 希望用户忽略组合对象与单个对象的不同,统一使用组合结构中的所有对象。
  • 树形菜单、文件目录、组织架构等场景。

2.3.4 java代码示例

以文件系统为例,文件和目录都是文件系统条目(FileSystemItem)。

java
import java.util.ArrayList;
import java.util.List;

// 组件接口
interface FileSystemItem {
    void print(String indent);
}

// 叶子节点:文件
class File implements FileSystemItem {
    private String name;

    public File(String name) {
        this.name = name;
    }

    @Override
    public void print(String indent) {
        System.out.println(indent + "File: " + name);
    }
}

// 组合节点:目录
class Directory implements FileSystemItem {
    private String name;
    private List<FileSystemItem> items = new ArrayList<>();

    public Directory(String name) {
        this.name = name;
    }

    public void add(FileSystemItem item) {
        items.add(item);
    }

    @Override
    public void print(String indent) {
        System.out.println(indent + "Directory: " + name);
        for (FileSystemItem item : items) {
            item.print(indent + "  ");
        }
    }
}

// 客户端代码
public class CompositeDemo {
    public static void main(String[] args) {
        Directory root = new Directory("root");
        root.add(new File("file1.txt"));

        Directory subDir = new Directory("subdir");
        subDir.add(new File("file2.txt"));
        root.add(subDir);

        root.print("");
    }
}

2.4 装饰器模式

2.4.1 为什么需要装饰器模式?

装饰器模式用于动态地给一个对象添加一些额外的职责,而不改变其结构。它比继承更灵活,因为可以在运行时添加或删除功能。

2.4.2 核心原理

装饰器模式通过创建一个装饰器类,包装原始类,并在保持原始类接口的前提下提供额外的功能。装饰器类与原始类实现相同的接口,并持有一个原始类的引用。这样,装饰器可以在调用原始方法前后添加自己的行为。

2.4.3 最佳实践场景

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 当不能使用子类进行扩展(例如,final类)或扩展会导致大量子类时。
  • Java IO流(如BufferedInputStream装饰FileInputStream)。

2.4.4 java代码示例

以咖啡为例,基础咖啡(SimpleCoffee)可以添加牛奶、糖等装饰。

java
// 组件接口
interface Coffee {
    double getCost();
    String getDescription();
}

// 具体组件
class SimpleCoffee implements Coffee {
    @Override
    public double getCost() {
        return 1.0;
    }

    @Override
    public String getDescription() {
        return "Simple coffee";
    }
}

// 抽象装饰器
abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee coffee) {
        this.decoratedCoffee = coffee;
    }

    public double getCost() {
        return decoratedCoffee.getCost();
    }

    public String getDescription() {
        return decoratedCoffee.getDescription();
    }
}

// 具体装饰器:加牛奶
class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public double getCost() {
        return super.getCost() + 0.5;
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", with milk";
    }
}

// 具体装饰器:加糖
class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public double getCost() {
        return super.getCost() + 0.2;
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", with sugar";
    }
}

// 客户端代码
public class DecoratorDemo {
    public static void main(String[] args) {
        Coffee coffee = new SimpleCoffee();
        coffee = new MilkDecorator(coffee);
        coffee = new SugarDecorator(coffee);

        System.out.println("Cost: " + coffee.getCost());
        System.out.println("Description: " + coffee.getDescription());
    }
}

2.5 外观模式

2.5.1 为什么需要外观模式?

外观模式提供了一个统一的接口,用来访问子系统中的一群接口。它定义了一个高层接口,让子系统更容易使用。当系统很复杂,包含多个子系统时,外观模式可以简化客户端的使用。

2.5.2 核心原理

外观模式通过引入一个外观类,将客户端的请求代理给适当的子系统对象,从而隐藏系统的复杂性。外观模式并不添加新功能,而是简化调用。

2.5.3 最佳实践场景

  • 为复杂的子系统提供一个简单接口。
  • 减少客户端与子系统的耦合。
  • 在多层结构中,可以使用外观模式定义每一层的入口。

2.5.4 java代码示例

假设有一个计算机启动过程,包括CPU、内存、硬盘等子系统。

java
// 子系统:CPU
class CPU {
    public void start() {
        System.out.println("CPU is starting...");
    }
}

// 子系统:Memory
class Memory {
    public void load() {
        System.out.println("Memory is loading...");
    }
}

// 子系统:HardDrive
class HardDrive {
    public void read() {
        System.out.println("HardDrive is reading...");
    }
}

// 外观类:Computer
class ComputerFacade {
    private CPU cpu;
    private Memory memory;
    private HardDrive hardDrive;

    public ComputerFacade() {
        this.cpu = new CPU();
        this.memory = new Memory();
        this.hardDrive = new HardDrive();
    }

    public void start() {
        cpu.start();
        memory.load();
        hardDrive.read();
        System.out.println("Computer started successfully!");
    }
}

// 客户端代码
public class FacadeDemo {
    public static void main(String[] args) {
        ComputerFacade computer = new ComputerFacade();
        computer.start();
    }
}

2.6 享元模式

2.6.1 为什么需要享元模式?

享元模式通过共享技术有效地支持大量细粒度对象的复用。当系统中存在大量相同或相似对象时,享元模式可以减少内存占用

2.6.2 核心原理

享元模式将对象的状态分为内部状态(intrinsic)和外部状态(extrinsic)。内部状态是共享的,不会随环境变化;外部状态是不共享的,由客户端在调用时传入。享元工厂负责创建和管理享元对象。

2.6.3 最佳实践场景

  • 系统中有大量相似对象,造成内存消耗过大。
  • 对象的大部分状态可以外部化。
  • 需要缓冲池的场景,如线程池、数据库连接池。

2.6.4 java代码示例

以文字编辑器为例,字符对象(Character)有字体、大小等内部状态,位置、颜色等外部状态。

java
import java.util.HashMap;
import java.util.Map;

// 享元接口
interface CharacterFlyweight {
    void display(String color, int x, int y);
}

// 具体享元:字符
class Character implements CharacterFlyweight {
    private char symbol; // 内部状态

    public Character(char symbol) {
        this.symbol = symbol;
    }

    @Override
    public void display(String color, int x, int y) {
        System.out.printf("Character: %c, Color: %s, Position: (%d, %d)\n", symbol, color, x, y);
    }
}

// 享元工厂
class CharacterFactory {
    private Map<Character, CharacterFlyweight> characters = new HashMap<>();

    public CharacterFlyweight getCharacter(char c) {
        CharacterFlyweight character = characters.get(c);
        if (character == null) {
            character = new Character(c);
            characters.put(c, character);
        }
        return character;
    }
}

// 客户端代码
public class FlyweightDemo {
    public static void main(String[] args) {
        CharacterFactory factory = new CharacterFactory();

        CharacterFlyweight charA = factory.getCharacter('A');
        charA.display("Red", 10, 20);

        CharacterFlyweight charB = factory.getCharacter('B');
        charB.display("Blue", 30, 40);

        // 再次获取'A'
        CharacterFlyweight charA2 = factory.getCharacter('A');
        charA2.display("Green", 50, 60); // 同一个对象,只是外部状态不同
    }
}

2.7 代理模式

2.7.1 为什么需要代理模式?

代理模式为其他对象提供一种代理以控制对这个对象的访问。当客户端不能直接访问某个对象时,可以通过代理来间接访问。代理可以在访问对象时添加额外的操作(如权限检查、延迟加载、日志等)。

2.7.2 核心原理

代理模式通过创建一个代理类,实现与原始类相同的接口,并持有原始类的引用。代理类在调用原始类方法前后可以添加额外的操作。代理模式分为静态代理和动态代理(如Java动态代理)。

2.7.3 最佳实践场景

  • 远程代理:为远程对象提供本地代表(如RMI)。
  • 虚拟代理:延迟加载大对象(如图片)。
  • 保护代理:控制对原始对象的访问权限。
  • 智能引用:在访问对象时添加额外操作(如引用计数、日志)。

2.7.4 java代码示例

以图片加载为例,使用虚拟代理实现延迟加载。

java
// 接口
interface Image {
    void display();
}

// 真实对象:大型图片
class RealImage implements Image {
    private String filename;

    public RealImage(String filename) {
        this.filename = filename;
        loadFromDisk();
    }

    private void loadFromDisk() {
        System.out.println("Loading image: " + filename);
    }

    @Override
    public void display() {
        System.out.println("Displaying image: " + filename);
    }
}

// 代理:虚拟代理(延迟加载)
class ProxyImage implements Image {
    private String filename;
    private RealImage realImage;

    public ProxyImage(String filename) {
        this.filename = filename;
    }

    @Override
    public void display() {
        if (realImage == null) {
            realImage = new RealImage(filename); // 在需要时才加载真实对象
        }
        realImage.display();
    }
}

// 客户端代码
public class ProxyDemo {
    public static void main(String[] args) {
        Image image = new ProxyImage("large_image.jpg");
        // 真实图片尚未加载
        System.out.println("Image will be displayed now:");
        image.display(); // 此时加载并显示
    }
}

3. 行为型模式

关注对象之间的职责分配、通信和协作,描述对象如何交互以及如何分配职责。

3.1 责任链模式

3.1.1 为什么需要?

当需要多个对象按顺序处理同一个请求时(如审批流程、过滤器链),避免请求发送者与多个处理者耦合。责任链模式将处理者连成链条,请求沿链传递直到被处理。

3.1.2 核心原理

  1. 处理器抽象:定义处理请求的接口和设置下一个处理者的方法
  2. 具体处理器:实现处理逻辑,若无法处理则转发给下一个处理器
  3. 链式传递:处理器对象形成链条,请求按顺序传递

3.1.3 最佳实践场景

  • 多级审批系统(请假审批)
  • Web请求过滤器(Servlet Filter)
  • 日志处理管道
  • 异常处理链路

3.1.4 java代码示例

java
// 1. 抽象处理器
abstract class Handler {
    protected Handler next;
    public void setNext(Handler next) { this.next = next; }
    public abstract void handleRequest(int request);
}

// 2. 具体处理器
class Manager extends Handler {
    @Override
    public void handleRequest(int amount) {
        if (amount <= 1000) {
            System.out.println("经理审批通过: " + amount);
        } else if (next != null) {
            next.handleRequest(amount); // 转交上级
        }
    }
}

class Director extends Handler {
    @Override
    public void handleRequest(int amount) {
        if (amount <= 5000) {
            System.out.println("总监审批通过: " + amount);
        } else if (next != null) {
            next.handleRequest(amount);
        }
    }
}

// 3. 客户端使用
public class Client {
    public static void main(String[] args) {
        Handler manager = new Manager();
        Handler director = new Director();
        manager.setNext(director); // 形成责任链
        
        // 处理请求
        manager.handleRequest(800);   // 经理处理
        manager.handleRequest(4500);  // 总监处理
    }
}

3.2 命令模式

3.2.1 为什么需要?

将请求封装为独立对象,支持请求排队、记录日志、撤销/重做等操作。解耦请求发起者与执行者,使系统更易扩展。

3.2.2 核心原理

  1. 命令接口:声明执行方法(如 execute()
  2. 具体命令:实现命令接口,绑定接收者与操作
  3. 调用者:触发命令的执行
  4. 接收者:实际执行操作的对象

3.2.3 最佳实践场景

  • GUI按钮/菜单操作
  • 事务管理(支持回滚)
  • 任务队列系统
  • 游戏操作控制

3.2.4 java代码示例

java
// 1. 命令接口
interface Command {
    void execute();
}

// 2. 接收者(实际执行者)
class Light {
    public void turnOn() { System.out.println("开灯"); }
    public void turnOff() { System.out.println("关灯"); }
}

// 3. 具体命令
class LightOnCommand implements Command {
    private Light light;
    public LightOnCommand(Light light) { this.light = light; }
    @Override
    public void execute() { light.turnOn(); }
}

// 4. 调用者(触发命令)
class RemoteControl {
    private Command command;
    public void setCommand(Command command) { this.command = command; }
    public void pressButton() { command.execute(); }
}

// 5. 客户端
public class Client {
    public static void main(String[] args) {
        Light light = new Light();
        Command lightOn = new LightOnCommand(light);
        
        RemoteControl remote = new RemoteControl();
        remote.setCommand(lightOn);
        remote.pressButton(); // 输出"开灯"
    }
}

3.3 解释器模式

3.3.1 为什么需要?

为特定领域问题(如SQL解析、正则表达式)定义语法规则,并解释执行语法规则。将复杂语法拆分为可管理的类结构。

3.3.2 核心原理

  1. 抽象表达式:声明解释操作的接口
  2. 终结符表达式:处理语法中的基本元素(如变量、常量)
  3. 非终结符表达式:组合表达式实现复杂逻辑(如运算、条件)
  4. 上下文环境:存储全局信息供表达式访问

3.3.3 最佳实践场景

  • 数学公式解析器
  • SQL语句解析
  • 编译器实现
  • 规则引擎(如风控规则)

3.3.4 java代码示例(实现 a + b 的解析)

java
// 1. 抽象表达式
interface Expression {
    int interpret(Context context);
}

// 2. 终结符表达式(变量)
class Variable implements Expression {
    private String name;
    public Variable(String name) { this.name = name; }
    @Override
    public int interpret(Context context) {
        return context.get(name);
    }
}

// 3. 非终结符表达式(加法)
class Add implements Expression {
    private Expression left, right;
    public Add(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }
    @Override
    public int interpret(Context context) {
        return left.interpret(context) + right.interpret(context);
    }
}

// 4. 上下文(存储变量值)
class Context {
    private Map<String, Integer> map = new HashMap<>();
    public void assign(String var, int value) { map.put(var, value); }
    public int get(String var) { return map.get(var); }
}

// 5. 客户端
public class Client {
    public static void main(String[] args) {
        Context context = new Context();
        context.assign("a", 10);
        context.assign("b", 5);
        
        Expression expr = new Add(new Variable("a"), new Variable("b"));
        System.out.println("结果: " + expr.interpret(context)); // 输出15
    }
}

3.4 迭代器模式

3.4.1 为什么需要?

提供统一的方式遍历集合对象(如列表、树),无需暴露集合内部结构。支持多种遍历方式(正序/倒序),符合单一职责原则。

3.4.2 核心原理

  1. 迭代器接口:定义 hasNext()next() 方法
  2. 具体迭代器:实现集合的遍历逻辑
  3. 集合接口:声明创建迭代器的方法
  4. 具体集合:实现集合结构并返回迭代器

3.4.3 最佳实践场景

  • 集合框架(Java的 Iterator
  • 树形结构遍历(深度优先/广度优先)
  • 数据库查询结果集遍历
  • 文件目录遍历

3.4.4 java代码示例(自定义集合迭代)

java
// 1. 迭代器接口
interface Iterator<T> {
    boolean hasNext();
    T next();
}

// 2. 集合接口
interface Container<T> {
    Iterator<T> createIterator();
}

// 3. 具体集合
class NameRepository implements Container<String> {
    private String[] names = {"Alice", "Bob", "Charlie"};
    
    @Override
    public Iterator<String> createIterator() {
        return new NameIterator();
    }
    
    // 4. 具体迭代器(内部类)
    private class NameIterator implements Iterator<String> {
        int index = 0;
        @Override
        public boolean hasNext() { return index < names.length; }
        @Override
        public String next() { return names[index++]; }
    }
}

// 5. 客户端
public class Client {
    public static void main(String[] args) {
        NameRepository repository = new NameRepository();
        Iterator<String> iter = repository.createIterator();
        
        while (iter.hasNext()) {
            System.out.println("Name: " + iter.next());
        }
        /* 输出:
           Name: Alice
           Name: Bob
           Name: Charlie */
    }
}

3.5 中介者模式

3.5.1 为什么需要

当多个对象存在复杂网状引用关系时(如聊天室中的用户互相调用),直接通信会导致耦合度高且难以维护。中介者模式通过引入中介对象来封装交互逻辑,将多对多关系转为一对多关系。

3.5.2 核心原理

定义一个中介接口封装对象间的交互,所有对象通过中介者通信,而非直接引用彼此。核心组件:

  1. Mediator(抽象中介者):定义通信接口
  2. ConcreteMediator(具体中介者):实现协调逻辑
  3. Colleague(同事类):持有中介者引用

3.5.3 最佳实践场景

  • 聊天室系统(用户间消息转发)
  • 飞机调度系统(塔台协调航班)
  • GUI组件交互(按钮/输入框联动)

3.5.4 java代码示例

java
// 1. 中介者接口
interface ChatMediator {
    void sendMessage(String msg, User user);
    void addUser(User user);
}

// 2. 具体中介者
class ChatRoom implements ChatMediator {
    private List<User> users = new ArrayList<>();
    public void addUser(User user) {
        users.add(user);
    }
    public void sendMessage(String msg, User sender) {
        for (User u : users) {
            if (u != sender) u.receive(msg); // 中介者转发消息
        }
    }
}

// 3. 同事类
abstract class User {
    protected ChatMediator mediator;
    protected String name;
    public User(ChatMediator med, String name) {
        this.mediator = med;
        this.name = name;
    }
    public abstract void send(String msg);
    public abstract void receive(String msg);
}

// 4. 具体同事类
class ChatUser extends User {
    public ChatUser(ChatMediator med, String name) {
        super(med, name);
    }
    public void send(String msg) {
        System.out.println(name + "发送: " + msg);
        mediator.sendMessage(msg, this); // 通过中介者发送
    }
    public void receive(String msg) {
        System.out.println(name + "收到: " + msg);
    }
}

// 使用
public class Main {
    public static void main(String[] args) {
        ChatMediator mediator = new ChatRoom();
        User user1 = new ChatUser(mediator, "Alice");
        User user2 = new ChatUser(mediator, "Bob");
        mediator.addUser(user1);
        mediator.addUser(user2);
        
        user1.send("你好!"); // 输出: Alice发送: 你好!  Bob收到: 你好!
    }
}

3.6 备忘录模式

3.6.1 为什么需要?

当需要保存对象状态以便后续恢复(如撤销操作、游戏存档)时,直接暴露对象内部状态会破坏封装性。备忘录模式提供状态快照管理机制,不破坏原始对象的封装。

3.6.2 核心原理

将状态保存在独立对象(备忘录)中,由原发器创建/恢复状态,管理者负责存储备忘录。核心组件:

  1. Originator(原发器):创建/恢复备忘录
  2. Memento(备忘录):存储状态快照
  3. Caretaker(管理者):存储和管理备忘录

3.6.3 最佳实践场景

  • 文本编辑器的撤销/重做
  • 游戏进度保存
  • 数据库事务回滚

3.6.4 java代码示例

java
// 1. 备忘录(存储状态)
class EditorMemento {
    private final String content;
    public EditorMemento(String content) {
        this.content = content;
    }
    public String getContent() {
        return content;
    }
}

// 2. 原发器(创建/恢复备忘录)
class TextEditor {
    private String content = "";
    public void type(String text) {
        content += text;
    }
    public EditorMemento save() {
        return new EditorMemento(content); // 创建快照
    }
    public void restore(EditorMemento m) {
        content = m.getContent(); // 恢复状态
    }
    public void print() {
        System.out.println("当前内容: " + content);
    }
}

// 3. 管理者
class History {
    private Stack<EditorMemento> states = new Stack<>();
    public void push(EditorMemento m) {
        states.push(m);
    }
    public EditorMemento pop() {
        return states.pop();
    }
}

// 使用
public class Main {
    public static void main(String[] args) {
        TextEditor editor = new TextEditor();
        History history = new History();
        
        editor.type("Hello");
        history.push(editor.save()); // 保存状态1
        
        editor.type(" World!");
        history.push(editor.save()); // 保存状态2
        
        editor.restore(history.pop()); // 撤销到状态1
        editor.print(); // 输出: 当前内容: Hello
    }
}

3.7 观察者模式

3.7.1 为什么需要?

对象状态变化需通知其他对象时(如新闻订阅),直接调用依赖对象会导致紧耦合。观察者模式建立发布-订阅机制,实现松耦合的状态同步。

3.7.2 核心原理

主题(Subject)维护观察者列表,状态变化时自动通知所有观察者。核心组件:

  1. Subject:注册/通知观察者
  2. Observer:定义更新接口
  3. ConcreteSubject:存储状态并触发通知
  4. ConcreteObserver:实现更新逻辑

3.7.3 最佳实践场景

  • 事件驱动系统(按钮点击事件)
  • 实时数据监控(股票价格更新)
  • 消息队列消费者

3.7.4 java代码示例

java
// 1. 观察者接口
interface NewsSubscriber {
    void update(String news);
}

// 2. 主题接口
interface NewsPublisher {
    void subscribe(NewsSubscriber sub);
    void unsubscribe(NewsSubscriber sub);
    void notifySubscribers();
}

// 3. 具体主题
class CNN implements NewsPublisher {
    private List<NewsSubscriber> subscribers = new ArrayList<>();
    private String latestNews;
    
    public void setNews(String news) {
        this.latestNews = news;
        notifySubscribers(); // 自动通知观察者
    }
    
    @Override
    public void subscribe(NewsSubscriber sub) {
        subscribers.add(sub);
    }
    
    @Override
    public void unsubscribe(NewsSubscriber sub) {
        subscribers.remove(sub);
    }
    
    @Override
    public void notifySubscribers() {
        for (NewsSubscriber sub : subscribers) {
            sub.update(latestNews);
        }
    }
}

// 4. 具体观察者
class User implements NewsSubscriber {
    private String name;
    public User(String name) {
        this.name = name;
    }
    @Override
    public void update(String news) {
        System.out.println(name + "收到新闻: " + news);
    }
}

// 使用
public class Main {
    public static void main(String[] args) {
        CNN cnn = new CNN();
        User alice = new User("Alice");
        User bob = new User("Bob");
        
        cnn.subscribe(alice);
        cnn.subscribe(bob);
        
        cnn.setNews("Java 21发布!"); 
        // 输出: Alice收到新闻: Java 21发布!
        //      Bob收到新闻: Java 21发布!
    }
}

3.8 状态模式

3.8.1 为什么需要?

对象行为随内部状态改变而改变(如订单状态流转),直接在代码中使用if-else判断状态会导致逻辑臃肿。状态模式将状态抽象为独立类,实现状态驱动的行为变化。

3.8.2 核心原理

将状态转移逻辑分散到不同状态类中,上下文(Context)委托当前状态对象执行行为。核心组件:

  1. State:抽象状态接口
  2. ConcreteState:具体状态实现类
  3. Context:持有当前状态,触发状态行为

3.8.3 最佳实践场景

  • 订单状态管理(待支付/已发货/已完成)
  • 电梯运行状态(停止/运行/故障)
  • 游戏角色状态(正常/中毒/眩晕)

3.8.4 java代码示例

java
// 1. 状态接口
interface OrderState {
    void next(Order order);
    void prev(Order order);
}

// 2. 具体状态类
class PaymentPendingState implements OrderState {
    @Override
    public void next(Order order) {
        order.setState(new ShippedState()); // 状态流转
        System.out.println("订单已发货");
    }
    @Override
    public void prev(Order order) {
        System.out.println("初始状态,无上一状态");
    }
}

class ShippedState implements OrderState {
    @Override
    public void next(Order order) {
        order.setState(new DeliveredState());
        System.out.println("订单已送达");
    }
    @Override
    public void prev(Order order) {
        order.setState(new PaymentPendingState());
        System.out.println("退回待支付状态");
    }
}

// 3. 上下文类
class Order {
    private OrderState state;
    public Order() {
        state = new PaymentPendingState(); // 初始状态
    }
    public void setState(OrderState state) {
        this.state = state;
    }
    public void nextState() {
        state.next(this); // 委托状态对象处理
    }
    public void prevState() {
        state.prev(this);
    }
}

// 使用
public class Main {
    public static void main(String[] args) {
        Order order = new Order();
        order.nextState(); // 输出: 订单已发货
        order.nextState(); // 输出: 订单已送达
        order.prevState(); // 输出: 退回待支付状态
    }
}

3.9 策略模式

3.9.1 为什么需要?

当系统需要在运行时动态选择算法行为时,避免使用大量条件语句(如if-else或switch)。策略模式将算法封装成独立对象,使它们可以互相替换,符合开闭原则。

3.9.2 核心原理

  1. 抽象策略接口:定义算法的公共接口
  2. 具体策略类:实现接口的具体算法
  3. 环境类(Context):持有策略引用,负责调用策略

3.9.3 最佳实践场景

  • 支付方式选择(微信/支付宝/银行卡)
  • 数据压缩算法切换(ZIP/RAR)
  • 导航路径计算(驾车/步行/公交)

3.9.4 Java代码示例

java
// 1. 策略接口
interface PaymentStrategy {
    void pay(int amount);
}

// 2. 具体策略实现
class AlipayStrategy implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("支付宝支付:" + amount + "元");
    }
}

class WechatPayStrategy implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("微信支付:" + amount + "元");
    }
}

// 3. 环境类
class PaymentContext {
    private PaymentStrategy strategy;
    
    public void setStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void executePayment(int amount) {
        strategy.pay(amount);
    }
}

// 4. 客户端使用
public class Client {
    public static void main(String[] args) {
        PaymentContext context = new PaymentContext();
        
        // 动态切换策略
        context.setStrategy(new AlipayStrategy());
        context.executePayment(100);
        
        context.setStrategy(new WechatPayStrategy());
        context.executePayment(200);
    }
}

3.10 模板方法模式

3.10.1 为什么需要?

多个类有相似的流程框架,但某些步骤实现不同时。模板方法模式定义算法骨架,将可变步骤延迟到子类实现,避免代码重复。

3.10.2 核心原理

  1. 抽象模板类:定义算法骨架(final模板方法)+ 抽象步骤
  2. 具体子类:实现抽象步骤,但不改变算法结构

3.10.3 最佳实践场景

  • 数据库操作流程(连接→执行→关闭)
  • 游戏初始化流程(加载资源→创建场景→启动循环)
  • 文档导出流程(准备数据→生成文件→压缩)

3.10.4 Java代码示例

java
// 1. 抽象模板类
abstract class DataExporter {
    // 模板方法(final防止子类重写)
    public final void export() {
        prepareData();
        generateFile();
        compressFile();
    }
    
    // 抽象步骤(子类实现)
    protected abstract void prepareData();
    protected abstract void generateFile();
    
    // 默认实现(可被子类覆盖)
    protected void compressFile() {
        System.out.println("默认压缩为ZIP格式");
    }
}

// 2. 具体实现类
class PDFExporter extends DataExporter {
    @Override
    protected void prepareData() {
        System.out.println("准备PDF数据...");
    }

    @Override
    protected void generateFile() {
        System.out.println("生成PDF文件");
    }
}

class ExcelExporter extends DataExporter {
    @Override
    protected void prepareData() {
        System.out.println("准备Excel数据...");
    }

    @Override
    protected void generateFile() {
        System.out.println("生成XLSX文件");
    }
    
    @Override
    protected void compressFile() {
        System.out.println("自定义压缩为RAR格式");
    }
}

// 3. 客户端使用
public class Client {
    public static void main(String[] args) {
        DataExporter exporter = new PDFExporter();
        exporter.export();  // 执行完整流程
        
        System.out.println("------");
        exporter = new ExcelExporter();
        exporter.export();
    }
}

3.11 访问者模式

3.11.1 为什么需要?

当需要对复杂对象结构(如组合结构)添加新操作,但不想修改元素类时。访问者模式将操作与元素结构分离,符合单一职责原则。

3.11.2 核心原理

  1. 访问者接口:声明访问操作
  2. 具体访问者:实现接口的操作
  3. 元素接口:定义accept(Visitor)方法
  4. 具体元素:实现accept方法,调用访问者

3.11.3 最佳实践场景

  • 编译器语法树分析(类型检查/代码优化)
  • 文件系统遍历(计算大小/搜索文件)
  • UI组件处理(渲染/校验)

3.11.4 Java代码示例

java
import java.util.ArrayList;
import java.util.List;

// 1. 元素接口
interface ComputerPart {
    void accept(ComputerPartVisitor visitor);
}

// 2. 具体元素
class Mouse implements ComputerPart {
    @Override
    public void accept(ComputerPartVisitor visitor) {
        visitor.visit(this);
    }
}

class Keyboard implements ComputerPart {
    @Override
    public void accept(ComputerPartVisitor visitor) {
        visitor.visit(this);
    }
}

// 3. 访问者接口
interface ComputerPartVisitor {
    void visit(Mouse mouse);
    void visit(Keyboard keyboard);
}

// 4. 具体访问者
class DisplayVisitor implements ComputerPartVisitor {
    @Override
    public void visit(Mouse mouse) {
        System.out.println("显示鼠标");
    }
    
    @Override
    public void visit(Keyboard keyboard) {
        System.out.println("显示键盘");
    }
}

class DiagnoseVisitor implements ComputerPartVisitor {
    @Override
    public void visit(Mouse mouse) {
        System.out.println("诊断鼠标...状态正常");
    }
    
    @Override
    public void visit(Keyboard keyboard) {
        System.out.println("诊断键盘...按键检测通过");
    }
}

// 5. 对象结构
class Computer implements ComputerPart {
    private List<ComputerPart> parts = new ArrayList<>();
    
    public Computer() {
        parts.add(new Mouse());
        parts.add(new Keyboard());
    }
    
    @Override
    public void accept(ComputerPartVisitor visitor) {
        for (ComputerPart part : parts) {
            part.accept(visitor);
        }
    }
}

// 6. 客户端使用
public class Client {
    public static void main(String[] args) {
        Computer computer = new Computer();
        
        // 应用不同访问者
        computer.accept(new DisplayVisitor());
        System.out.println("------");
        computer.accept(new DiagnoseVisitor());
    }
}