设计模式
软件设计中的设计模式是解决特定场景下常见设计问题的、可复用的、经过验证的最佳实践方案。它们不是可以直接转化为代码的完整解决方案,而是提供了设计蓝图和指导原则,帮助你构建更灵活、可维护、可扩展和可复用的软件。
最著名和基础的设计模式分类来源于“四人帮” 的经典著作《设计模式:可复用面向对象软件的基础》,其中描述了 23 种核心模式,通常分为三大类:创建型模式、结构型模式和行为型模式。
1. 创建型模式
关注对象的创建机制,旨在以更灵活、更符合约束的方式创建对象,而不是直接使用 new 操作符。
1.1 单例模式 (Singleton Pattern)
1.1.1 为什么需要单例模式?
当系统中需要确保一个类只有一个实例(如配置管理器、线程池),避免资源冲突或重复创建时使用。核心是控制实例数量,节省内存资源。
1.1.2 核心原理
- 私有化构造函数(禁止外部
new
) - 静态私有成员变量保存唯一实例
- 静态公有方法提供全局访问点
1.1.3 最佳实践场景
- 数据库连接池
- 日志记录器
- 应用配置对象
- 设备管理器(如打印机)
1.1.4 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 最佳实践场景
- 日志记录器(文件/数据库日志)
- 支付系统(支付宝/微信支付)
- 跨平台UI组件创建
1.2.4 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.3.3 最佳实践场景
- 跨平台GUI库(Windows/Mac按钮+文本框)
- 数据库访问套件(连接+命令+读取器)
- 游戏角色装备系统(武器+防具)
1.3.4 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 核心原理
- 将对象构建拆分为多个步骤
- 指挥者(Director)控制构建流程
- 建造者(Builder)实现具体构建
- 产品与构建过程分离
1.4.3 最佳实践场景
- 生成复杂报表(标题/表头/数据体)
- 创建HTML文档(head/body元素)
- 快餐套餐定制(主食+饮料+甜点)
1.4.4 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 核心原理
- 实现
Cloneable
接口 - 重写
clone()
方法 - 深拷贝与浅拷贝控制
1.5.3 最佳实践场景
- 游戏场景中大量相似NPC创建
- 撤销/重做功能的状态保存
- 复杂配置对象的快速复制
1.5.4 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)
方法。
// 目标接口(新接口)
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分离。
// 实现部分接口:绘图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)。
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)可以添加牛奶、糖等装饰。
// 组件接口
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、内存、硬盘等子系统。
// 子系统: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)有字体、大小等内部状态,位置、颜色等外部状态。
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代码示例
以图片加载为例,使用虚拟代理实现延迟加载。
// 接口
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 核心原理
- 处理器抽象:定义处理请求的接口和设置下一个处理者的方法
- 具体处理器:实现处理逻辑,若无法处理则转发给下一个处理器
- 链式传递:处理器对象形成链条,请求按顺序传递
3.1.3 最佳实践场景
- 多级审批系统(请假审批)
- Web请求过滤器(Servlet Filter)
- 日志处理管道
- 异常处理链路
3.1.4 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 核心原理
- 命令接口:声明执行方法(如
execute()
) - 具体命令:实现命令接口,绑定接收者与操作
- 调用者:触发命令的执行
- 接收者:实际执行操作的对象
3.2.3 最佳实践场景
- GUI按钮/菜单操作
- 事务管理(支持回滚)
- 任务队列系统
- 游戏操作控制
3.2.4 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 核心原理
- 抽象表达式:声明解释操作的接口
- 终结符表达式:处理语法中的基本元素(如变量、常量)
- 非终结符表达式:组合表达式实现复杂逻辑(如运算、条件)
- 上下文环境:存储全局信息供表达式访问
3.3.3 最佳实践场景
- 数学公式解析器
- SQL语句解析
- 编译器实现
- 规则引擎(如风控规则)
3.3.4 java代码示例(实现 a + b 的解析)
// 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 核心原理
- 迭代器接口:定义
hasNext()
和next()
方法 - 具体迭代器:实现集合的遍历逻辑
- 集合接口:声明创建迭代器的方法
- 具体集合:实现集合结构并返回迭代器
3.4.3 最佳实践场景
- 集合框架(Java的
Iterator
) - 树形结构遍历(深度优先/广度优先)
- 数据库查询结果集遍历
- 文件目录遍历
3.4.4 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 核心原理
定义一个中介接口封装对象间的交互,所有对象通过中介者通信,而非直接引用彼此。核心组件:
Mediator
(抽象中介者):定义通信接口ConcreteMediator
(具体中介者):实现协调逻辑Colleague
(同事类):持有中介者引用
3.5.3 最佳实践场景
- 聊天室系统(用户间消息转发)
- 飞机调度系统(塔台协调航班)
- GUI组件交互(按钮/输入框联动)
3.5.4 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 核心原理
将状态保存在独立对象(备忘录)中,由原发器创建/恢复状态,管理者负责存储备忘录。核心组件:
Originator
(原发器):创建/恢复备忘录Memento
(备忘录):存储状态快照Caretaker
(管理者):存储和管理备忘录
3.6.3 最佳实践场景
- 文本编辑器的撤销/重做
- 游戏进度保存
- 数据库事务回滚
3.6.4 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)维护观察者列表,状态变化时自动通知所有观察者。核心组件:
Subject
:注册/通知观察者Observer
:定义更新接口ConcreteSubject
:存储状态并触发通知ConcreteObserver
:实现更新逻辑
3.7.3 最佳实践场景
- 事件驱动系统(按钮点击事件)
- 实时数据监控(股票价格更新)
- 消息队列消费者
3.7.4 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)委托当前状态对象执行行为。核心组件:
State
:抽象状态接口ConcreteState
:具体状态实现类Context
:持有当前状态,触发状态行为
3.8.3 最佳实践场景
- 订单状态管理(待支付/已发货/已完成)
- 电梯运行状态(停止/运行/故障)
- 游戏角色状态(正常/中毒/眩晕)
3.8.4 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 核心原理
- 抽象策略接口:定义算法的公共接口
- 具体策略类:实现接口的具体算法
- 环境类(Context):持有策略引用,负责调用策略
3.9.3 最佳实践场景
- 支付方式选择(微信/支付宝/银行卡)
- 数据压缩算法切换(ZIP/RAR)
- 导航路径计算(驾车/步行/公交)
3.9.4 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 核心原理
- 抽象模板类:定义算法骨架(final模板方法)+ 抽象步骤
- 具体子类:实现抽象步骤,但不改变算法结构
3.10.3 最佳实践场景
- 数据库操作流程(连接→执行→关闭)
- 游戏初始化流程(加载资源→创建场景→启动循环)
- 文档导出流程(准备数据→生成文件→压缩)
3.10.4 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 核心原理
- 访问者接口:声明访问操作
- 具体访问者:实现接口的操作
- 元素接口:定义accept(Visitor)方法
- 具体元素:实现accept方法,调用访问者
3.11.3 最佳实践场景
- 编译器语法树分析(类型检查/代码优化)
- 文件系统遍历(计算大小/搜索文件)
- UI组件处理(渲染/校验)
3.11.4 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());
}
}