北屋教程网

专注编程知识分享,从入门到精通的编程学习平台

Java 3大设计模式:责任链/工厂/观察者原理和使用过场景精讲

我们来深入、详尽地探讨Java的三大核心设计模式:责任链模式、工厂模式(涵盖其多种形态)和观察者模式。本文将超过5000字,包含详细的原理剖析、丰富的代码示例、实际应用场景以及深度精讲。


第一部分:责任链模式 (Chain of Responsibility Pattern)

一、 原理与核心思想

责任链模式是一种行为设计模式,其核心思想是将请求的发送者和接收者解耦。它允许你将多个处理对象(称为处理器或处理节点)连接成一条链。当一个请求被发出后,它会沿着这条链传递,直到链上的某个对象决定处理它为止。

关键角色:

  1. 抽象处理器 (Handler): 定义一个处理请求的接口(或抽象类)。通常包含一个指向下一个处理器的引用(nextHandler)和一个处理请求的方法(如 handleRequest)。
  2. 具体处理器 (Concrete Handler): 实现了抽象处理器的接口,负责处理它所能负责的请求。如果自己不能处理,则将请求转发给下一个处理器。
  3. 客户端 (Client): 创建处理链,并将请求发送到链上的第一个处理器。

UML 示意图:

text

[Client] --> [Handler]
                     | - nextHandler: Handler
                     | + handleRequest()
                     | + setNext()
                     / \
                    /   \
       [ConcreteHandlerA] [ConcreteHandlerB]
       + handleRequest()  + handleRequest()

二、 使用场景

  1. 多级审批流程: 如请假审批,需要经过项目经理、部门经理、总经理等层级,每个层级有各自的审批权限。
  2. 异常处理: Java中的 try-catch 块就是一个责任链。异常会从最内层的 catch 开始匹配,直到找到能处理该异常的类型为止。
  3. 过滤器/拦截器链: 在Web开发中极为常见,如Servlet中的 FilterChain、Spring MVC中的 HandlerInterceptor。一个HTTP请求需要经过一系列过滤器(字符编码、安全校验、日志记录等)的处理。
  4. 日志记录器: 不同的日志级别(DEBUG, INFO, WARN, ERROR)可以被不同层级的日志处理器处理(控制台输出、文件记录、发送警报等)。

三、 代码示例:多级审批系统

我们来实现一个员工请假审批的系统。请假天数不同,需要不同级别的领导审批。

1. 抽象处理器:审批者类 (Approver)

java

/**
 * 抽象审批者(抽象处理器)
 */
public abstract class Approver {
    // 核心:指向下一个处理者的引用
    protected Approver nextApprover;

    // 设置下一个审批者
    public void setNext(Approver nextApprover) {
        this.nextApprover = nextApprover;
    }

    // 处理审批请求的方法
    public abstract void processRequest(LeaveRequest request);
}

2. 具体处理器:各级领导 (ProjectManager, DepartmentManager, CEO)

java

/**
 * 具体处理器:项目经理
 */
public class ProjectManager extends Approver {
    @Override
    public void processRequest(LeaveRequest request) {
        if (request.getDays() <= 3) {
            System.out.println("项目经理批准了 " + request.getEmployeeName() + " 的 " + request.getDays() + " 天假期。");
        } else {
            // 自己无法处理,传递给下一个
            if (nextApprover != null) {
                System.out.println("项目经理无权限审批,转交上级...");
                nextApprover.processRequest(request);
            } else {
                System.out.println("请假申请无人可处理!");
            }
        }
    }
}

/**
 * 具体处理器:部门经理
 */
public class DepartmentManager extends Approver {
    @Override
    public void processRequest(LeaveRequest request) {
        if (request.getDays() <= 7) {
            System.out.println("部门经理批准了 " + request.getEmployeeName() + " 的 " + request.getDays() + " 天假期。");
        } else {
            if (nextApprover != null) {
                System.out.println("部门经理无权限审批,转交上级...");
                nextApprover.processRequest(request);
            } else {
                System.out.println("请假申请无人可处理!");
            }
        }
    }
}

/**
 * 具体处理器:CEO
 */
public class CEO extends Approver {
    @Override
    public void processRequest(LeaveRequest request) {
        if (request.getDays() <= 15) {
            System.out.println("CEO批准了 " + request.getEmployeeName() + " 的 " + request.getDays() + " 天假期。");
        } else {
            System.out.println("CEO驳回了 " + request.getEmployeeName() + " 的 " + request.getDays() + " 天假期申请,假期太长!");
            // CEO是最终节点,没有nextApprover,链到此结束
        }
    }
}

3. 请求类 (LeaveRequest)

java

/**
 * 请假请求
 */
public class LeaveRequest {
    private String employeeName;
    private int days;

    public LeaveRequest(String employeeName, int days) {
        this.employeeName = employeeName;
        this.days = days;
    }
    // Getter methods...
    public String getEmployeeName() { return employeeName; }
    public int getDays() { return days; }
}

4. 客户端:组装责任链并提交请求

java

/**
 * 客户端
 */
public class ChainOfResponsibilityClient {
    public static void main(String[] args) {
        // 1. 创建处理节点
        Approver pm = new ProjectManager();
        Approver dm = new DepartmentManager();
        Approver ceo = new CEO();

        // 2. 组装责任链:项目经理 -> 部门经理 -> CEO
        pm.setNext(dm);
        dm.setNext(ceo);
        // 注意:CEO后面没有设置next,它是链的末端

        // 3. 创建不同的请假请求
        LeaveRequest request1 = new LeaveRequest("张三", 2);
        LeaveRequest request2 = new LeaveRequest("李四", 5);
        LeaveRequest request3 = new LeaveRequest("王五", 10);
        LeaveRequest request4 = new LeaveRequest("赵六", 20);

        System.out.println("===== 处理张三的2天申请 =====");
        pm.processRequest(request1); // 应由项目经理处理

        System.out.println("\n===== 处理李四的5天申请 =====");
        pm.processRequest(request2); // 项目经理 -> 部门经理

        System.out.println("\n===== 处理王五的10天申请 =====");
        pm.processRequest(request3); // 项目经理 -> 部门经理 -> CEO

        System.out.println("\n===== 处理赵六的20天申请 =====");
        pm.processRequest(request4); // 项目经理 -> 部门经理 -> CEO (驳回)
    }
}

输出结果:

text

===== 处理张三的2天申请 =====
项目经理批准了 张三 的 2 天假期。

===== 处理李四的5天申请 =====
项目经理无权限审批,转交上级...
部门经理批准了 李四 的 5 天假期。

===== 处理王五的10天申请 =====
项目经理无权限审批,转交上级...
部门经理无权限审批,转交上级...
CEO批准了 王五 的 10 天假期。

===== 处理赵六的20天申请 =====
项目经理无权限审批,转交上级...
部门经理无权限审批,转交上级...
CEO驳回了 赵六 的 20 天假期申请,假期太长!

四、 精讲与深入

  • 优点:
    • 降低耦合度: 客户端无需知道请求由谁处理,处理者也不知道链的结构。双方都只依赖于抽象接口。
    • 增强灵活性: 可以动态地增加、修改或删除链上的处理者,符合开闭原则。
    • 简化对象: 每个处理者只需关注自己职责范围内的请求,无需关心其他逻辑。
  • 缺点:
    • 请求不一定被处理: 如果链配置不完整,请求可能到达链的末端也无法被处理。
    • 性能影响: 链过长可能会影响性能,特别是在递归调用深度很大时。
    • 调试困难: 请求的传递是沿着链进行的,调试时逻辑可能不那么直观。
  • 变体:
    • 功能链: 每个处理器都对请求做一些处理,然后传递给下一个,而不是选择性地处理。Web Filter就是典型例子。
    • 提前终止: 处理器处理完后,可以决定是否继续传递请求。

第二部分:工厂模式 (Factory Pattern)

工厂模式是一个创建型模式的家族,它提供了一种创建对象的最佳方式,而无需将实例化逻辑暴露给客户端。它主要分为三种:简单工厂模式、工厂方法模式、抽象工厂模式

一、 简单工厂模式 (Simple Factory Pattern)

原理: 定义一个工厂类,它根据传入的参数,动态决定创建哪一种产品类的实例。

代码示例:图形工厂

java

// 1. 产品接口
public interface Shape {
    void draw();
}

// 2. 具体产品
public class Circle implements Shape {
    @Override
    public void draw() { System.out.println("Drawing a Circle"); }
}
public class Rectangle implements Shape {
    @Override
    public void draw() { System.out.println("Drawing a Rectangle"); }
}

// 3. 简单工厂
public class ShapeFactory {
    // 核心工厂方法,根据类型字符串创建对象
    public static Shape createShape(String type) {
        if (type == null) return null;
        if (type.equalsIgnoreCase("CIRCLE")) {
            return new Circle();
        } else if (type.equalsIgnoreCase("RECTANGLE")) {
            return new Rectangle();
        }
        // 如果需要新增产品,必须修改这里的代码,违反开闭原则
        throw new IllegalArgumentException("Unknown shape type: " + type);
    }
}

// 4. 客户端
public class Client {
    public static void main(String[] args) {
        Shape circle = ShapeFactory.createShape("CIRCLE");
        circle.draw(); // Output: Drawing a Circle

        Shape rectangle = ShapeFactory.createShape("RECTANGLE");
        rectangle.draw(); // Output: Drawing a Rectangle
    }
}

精讲: 简单工厂违背了“开闭原则”(对扩展开放,对修改关闭)。每次新增产品都需要修改工厂类的逻辑。但它简单易懂,适用于产品类型不多且稳定的情况。


二、 工厂方法模式 (Factory Method Pattern)

原理: 定义一个用于创建对象的接口(工厂接口),但让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

代码示例:日志记录器工厂
假设我们需要一个日志系统,可以支持将日志记录到本地文件或者数据库。

java

// 1. 产品接口
public interface Logger {
    void log(String message);
}

// 2. 具体产品
public class FileLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("Logging to FILE: " + message);
    }
}
public class DatabaseLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("Logging to DATABASE: " + message);
    }
}

// 3. 抽象工厂(创建者)
public abstract class LoggerFactory {
    // 工厂方法,由子类实现
    public abstract Logger createLogger();

    // 还可以在抽象工厂中定义一些公共逻辑
    public void logMessage(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 {
    @Override
    public Logger createLogger() {
        // 可能包含数据库连接等逻辑
        return new DatabaseLogger();
    }
}

// 5. 客户端
public class Client {
    public static void main(String[] args) {
        LoggerFactory factory;

        // 根据配置或环境决定使用哪个工厂
        String config = "database"; // 可以来自配置文件

        if ("database".equals(config)) {
            factory = new DatabaseLoggerFactory();
        } else {
            factory = new FileLoggerFactory();
        }

        // 使用工厂创建产品并使用
        factory.logMessage("This is a log message!");
        // Output: Logging to DATABASE: This is a log message!
    }
}

精讲:

  • 符合开闭原则: 要新增一种日志方式(如网络日志),只需新增一个 NetworkLogger 和一个 NetworkLoggerFactory,无需修改任何现有代码。
  • 核心是“继承”: 通过让子类实现工厂方法来决定创建何种对象,将对象创建的责任完全委托给了子类。
  • JDK中的应用: java.util.Calendar#getInstance(), java.text.NumberFormat#getInstance() 等方法本质上使用了工厂方法模式。

三、 抽象工厂模式 (Abstract Factory Pattern)

原理: 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。它是工厂的工厂。

代码示例:GUI 皮肤库
假设我们要开发一个跨平台的GUI工具包,需要为不同操作系统(Windows, Mac)创建一套UI控件(按钮、复选框)。

java

// 1. 抽象产品族
public interface Button {
    void render();
}
public interface Checkbox {
    void render();
}

// 2. 具体产品族 - Windows系列
public class WindowsButton implements Button {
    @Override public void render() { System.out.println("Rendering a Windows-style Button"); }
}
public class WindowsCheckbox implements Checkbox {
    @Override public void render() { System.out.println("Rendering a Windows-style Checkbox"); }
}

// 3. 具体产品族 - Mac系列
public class MacButton implements Button {
    @Override public void render() { System.out.println("Rendering a Mac-style Button"); }
}
public class MacCheckbox implements Checkbox {
    @Override public void render() { System.out.println("Rendering a Mac-style Checkbox"); }
}

// 4. 抽象工厂 (核心)
public interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

// 5. 具体工厂 - Windows工厂
public class WindowsFactory implements GUIFactory {
    @Override public Button createButton() { return new WindowsButton(); }
    @Override public Checkbox createCheckbox() { return new WindowsCheckbox(); }
}

// 6. 具体工厂 - Mac工厂
public class MacFactory implements GUIFactory {
    @Override public Button createButton() { return new MacButton(); }
    @Override public Checkbox createCheckbox() { return new MacCheckbox(); }
}

// 7. 客户端代码。它只依赖于抽象工厂和抽象产品。
public class Application {
    private Button button;
    private Checkbox checkbox;

    public Application(GUIFactory factory) {
        // 客户端不知道它创建的是什么具体产品,只知道它们是Button和Checkbox
        this.button = factory.createButton();
        this.checkbox = factory.createCheckbox();
    }

    public void render() {
        button.render();
        checkbox.render();
    }
}

// 8. 程序的配置部分,根据运行时环境选择工厂
public class ApplicationConfigurator {
    public static void main(String[] args) {
        GUIFactory factory;
        String osName = System.getProperty("os.name").toLowerCase();

        if (osName.contains("win")) {
            factory = new WindowsFactory();
        } else if (osName.contains("mac")) {
            factory = new MacFactory();
        } else {
            throw new RuntimeException("Unknown operating system.");
        }

        Application app = new Application(factory);
        app.render();
        /* Output on Windows:
           Rendering a Windows-style Button
           Rendering a Windows-style Checkbox
         */
    }
}

精讲:

  • 解决“产品族”创建问题: 它强调创建一整套相关的对象(例如,要么全部是Windows风格,要么全部是Mac风格),保证产品之间的兼容性。
  • 与工厂方法的区别:
    • 工厂方法: 针对一个产品等级结构(Logger)。
    • 抽象工厂: 针对多个产品等级结构(Button, Checkbox...),形成一个产品族。
  • 缺点: 难以支持新种类的产品。如果要在 GUIFactory 中新增一个 createTextBox() 方法,那么所有具体工厂及其所有子类都需要修改,这违反了开闭原则。

第三部分:观察者模式 (Observer Pattern)

一、 原理与核心思想

观察者模式是一种行为设计模式,它定义了对象间的一种一对多的依赖关系。当一个对象(称为主题Subject)的状态发生改变时,所有依赖于它的对象(称为观察者Observers)都会得到通知并自动更新。

关键角色:

  1. 主题 (Subject): 持有观察者的引用集合,提供注册、注销观察者的方法,以及通知观察者的方法。
  2. 具体主题 (Concrete Subject): 实现主题接口,在状态改变时通知所有注册的观察者。
  3. 观察者 (Observer): 定义一个更新接口,用于在主题状态改变时接收通知。
  4. 具体观察者 (Concrete Observer): 实现观察者接口,维护一个对具体主题的引用,并存储与主题相关的状态,这些状态应与主题的状态保持一致。

二、 使用场景

  1. 事件驱动系统: GUI编程中的按钮点击、鼠标移动等事件监听。
  2. 发布-订阅模型: 消息队列、新闻推送、微博/微信公众号关注。
  3. 模型-视图-控制器 (MVC) 架构: 当模型(数据)发生变化时,会自动通知相关的视图进行更新。
  4. 监控系统: 当被监控的指标(如CPU使用率)达到阈值时,通知警报器。

三、 代码示例:气象站监测系统

这是一个经典例子:一个气象站(主题)负责追踪温度、湿度、气压等数据。多个布告板(观察者)需要实时显示最新的数据。

1. 主题接口 (Subject)

java

public interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers(); // 通知所有观察者
}

2. 观察者接口 (Observer)

java

public interface Observer {
    // 当主题状态改变时,调用此方法。可以传递参数,也可以用“拉”的模式让观察者自己从主题获取数据。
    void update(float temperature, float humidity, float pressure);
}

3. 布告板显示接口 (DisplayElement) - 可选

java

public interface DisplayElement {
    void display(); // 显示数据
}

4. 具体主题:WeatherData

java

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

public class WeatherData implements Subject {
    private List<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {
        this.observers = new ArrayList<>();
    }

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    @Override
    public void notifyObservers() {
        // 遍历所有观察者,并调用其update方法
        for (Observer observer : observers) {
            observer.update(temperature, humidity, pressure);
        }
    }

    // 当从气象站得到新的测量数据时,调用此方法
    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged(); // 数据改变,通知观察者
    }

    private void measurementsChanged() {
        notifyObservers();
    }

    // 其他Getter方法...
}

5. 具体观察者:当前条件布告板 (CurrentConditionsDisplay)

java

public class CurrentConditionsDisplay implements Observer, DisplayElement {
    private float temperature;
    private float humidity;
    private Subject weatherData; // 保留对Subject的引用,方便以后注销

    // 构造函数中注册自己
    public CurrentConditionsDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        // 当收到通知时,更新数据
        this.temperature = temperature;
        this.humidity = humidity;
        display(); // 然后显示
    }

    @Override
    public void display() {
        System.out.printf("Current conditions: %.1f°C and %.1f%% humidity%n", temperature, humidity);
    }
}

6. 具体观察者:统计布告板 (StatisticsDisplay) - 另一个观察者

java

public class StatisticsDisplay implements Observer, DisplayElement {
    private float maxTemp = 0.0f;
    private float minTemp = 200;
    private float tempSum = 0.0f;
    private int numReadings;
    private Subject weatherData;

    public StatisticsDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        tempSum += temperature;
        numReadings++;

        if (temperature > maxTemp) maxTemp = temperature;
        if (temperature < minTemp) minTemp = temperature;

        display();
    }

    @Override
    public void display() {
        System.out.printf("Avg/Max/Min temperature = %.1f/%.1f/%.1f%n",
                (tempSum / numReadings), maxTemp, minTemp);
    }
}

7. 测试程序(客户端)

java

public class WeatherStation {
    public static void main(String[] args) {
        // 创建主题(被观察者)
        WeatherData weatherData = new WeatherData();

        // 创建观察者,并在构造时注册到主题
        CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);

        // 模拟新的气象测量数据到达
        System.out.println("--- First Measurement ---");
        weatherData.setMeasurements(26, 65, 1013);
        System.out.println();

        System.out.println("--- Second Measurement ---");
        weatherData.setMeasurements(27, 70, 1012);
        System.out.println();

        System.out.println("--- Third Measurement ---");
        weatherData.setMeasurements(25, 90, 1014);

        // 可以动态移除观察者
        // weatherData.removeObserver(statisticsDisplay);
    }
}

输出结果:

text

--- First Measurement ---
Current conditions: 26.0°C and 65.0% humidity
Avg/Max/Min temperature = 26.0/26.0/26.0

--- Second Measurement ---
Current conditions: 27.0°C and 70.0% humidity
Avg/Max/Min temperature = 26.5/27.0/26.0

--- Third Measurement ---
Current conditions: 25.0°C and 90.0% humidity
Avg/Max/Min temperature = 26.0/27.0/25.0

四、 精讲与深入 (含Java内置支持)

  • 推 (Push) vs 拉 (Pull) 模型:
    • 推模型(如上例): 主题将数据直接通过 update() 方法的参数推送给观察者。简单直接,但可能推送观察者不需要的数据。
    • 拉模型: 主题只通知观察者“我变了”,观察者收到通知后,主动调用主题的Getter方法来“拉取”它需要的数据。更灵活,但观察者需要持有主题的引用。Java内置的观察者模式使用拉模型。
  • Java内置的观察者模式 (java.util.Observer & java.util.Observable):
    • Observable 类相当于 Subject
    • Observer 接口相当于我们的 Observer
    • 不推荐使用! 因为 Observable 是一个而不是接口,这限制了它的使用(Java不支持多重继承)。而且它的 setChanged() 方法是 protected 的,意味着你不能在不继承 Observable 的情况下组合使用它。自Java 9起,它已被标记为 @Deprecated(since="9")
  • 优点:
    • 解耦: 主题和观察者之间是抽象耦合,主题不知道观察者的具体细节。
    • 支持广播通信: 一个主题的改变可以通知任意数量的观察者。
  • 缺点:
    • 意外的更新: 观察者不知道其他观察者的存在,一个观察者的操作可能触发一系列连锁反应,导致系统难以跟踪和调试。
    • 性能考虑: 如果观察者很多,或者更新操作非常耗时,通知所有观察者可能会成为性能瓶颈。
  • 现代实现: 在现代Java开发中,更推荐使用事件监听机制或反应式编程库(如Project Reactor, RxJava)来实现观察者模式,它们提供了更强大、更灵活、异步非阻塞的支持。

总结与对比

模式

类型

核心目的

关键思想

责任链模式

行为型

解耦请求发送与接收,让多个对象都有机会处理请求

构建一条处理链,请求沿链传递,直至被处理

工厂模式

创建型

解耦对象创建与使用

new 操作封装起来,由工厂负责实例化

观察者模式

行为型

定义一对多的依赖,实现状态变化的通知

主题状态改变,自动通知所有依赖它的观察者

这三大模式从不同维度解决了软件设计中的核心问题:

  • 责任链解决了行为请求的传递与处理问题。
  • 工厂解决了对象创建的复杂性和依赖问题。
  • 观察者解决了对象状态的同步与通知问题。

它们都深刻地体现了面向对象设计原则,尤其是依赖倒置原则(DIP)开闭原则(OCP)单一职责原则(SRP)。熟练掌握这三种模式,是构建灵活、可维护、可扩展的Java应用程序的基石。本文通过超过5000字的详细讲解和代码示例,希望能帮助你彻底理解并掌握它们。

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言