Java后端--设计模式基础
一、设计模式的含义
设计模式其实是一种编程经验的总结,是面对一些特定问题时,程序员们经过长期实践和思考得出的解决方案。可以把它看作是“最佳实践”,它们并不是具体的代码,而是解决问题的一套思路和结构。
二、设计模式的作用
- 简化代码结构:设计模式帮助你把复杂的问题拆解成多个简单、可复用的模块。
- 提升代码灵活性:通过设计模式,可以轻松地应对需求变化,增加功能时不需要大改代码。
- 提高代码的可维护性:结构化的代码更容易理解、修改,减少了维护过程中出错的可能性。
- 增强团队合作:设计模式是被广泛使用的,当多个开发人员合作时,它们提供了一种共同的语言和思路,减少沟通成本。
三、五种常见的设计模式
1. 单例模式(Singleton Pattern)
目的:
- 控制类的唯一实例:单例模式旨在确保某个类在系统中只创建一次,并且所有对该类的访问都共享这个唯一的实例。
解决问题:
- 资源浪费和不一致性:在一些场景中,比如数据库连接池、日志管理器、配置管理,系统的多个部分需要访问同一对象。如果每个部分都创建自己的实例,不仅浪费资源,而且容易导致状态不一致。单例模式通过创建一个唯一的全局实例,避免了多次创建相同对象的开销,并确保各个部分访问的是相同的资源。
如何使用:
public class Singleton {
private static Singleton instance; // 静态变量,保存唯一实例
private Singleton() {} // 私有构造方法,防止外部实例化
// 提供全局访问点
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Singleton
类使用了私有构造函数,确保外部不能通过new
来创建对象。- 通过
getInstance()
方法,控制类的实例创建。只有在调用时,才会创建实例(延迟加载)。instance
是静态变量,保证在类的生命周期内始终保持一个唯一的实例,所有对该类的访问都将使用同一个实例。
2. 工厂模式(Factory Pattern)
目的:
- 封装对象创建:工厂模式通过一个统一的接口来创建对象,避免客户端直接依赖具体类的构造过程。
解决问题:
- 复杂对象的创建与管理:当对象的创建过程复杂或需要动态选择具体实现时,工厂模式能把这些复杂性封装在工厂类中。这样,客户端代码只需要关心对象的使用,不需要关心对象是如何创建的。例如,在图形界面中,根据用户输入创建不同的控件类型(如按钮、文本框),而不是让客户端自己去决定和实现这些对象的创建细节。工厂模式通过解耦对象创建过程,提升了代码的灵活性和可维护性。
如何使用:
public class ShapeFactory {
// 根据传入的类型创建对象
public Shape getShape(String shapeType) {
if (shapeType.equalsIgnoreCase("CIRCLE")) {
return new Circle();
} else if (shapeType.equalsIgnoreCase("RECTANGLE")) {
return new Rectangle();
}
return null;
}
}
ShapeFactory
类负责创建不同类型的Shape
对象。- 根据传入的
shapeType
参数,动态选择创建相应的对象。- 工厂方法
getShape()
封装了对象的创建逻辑,客户端只需调用工厂方法,而不需要知道具体的类名和创建细节。
3. 策略模式(Strategy Pattern)
目的:
- 动态替换算法:策略模式旨在将一组算法封装在独立的类中,客户端可以根据需要动态选择使用哪种算法或行为,而不需要在代码中进行复杂的条件判断。
解决问题:
- 条件判断过多导致的复杂性:在实际项目中,如果你需要在运行时根据不同条件执行不同的算法或行为,通常会使用大量的
if-else
或switch-case
语句,这不仅让代码显得复杂、难以维护,也不符合开闭原则(对扩展开放,对修改关闭)。策略模式通过将不同的算法封装成独立的策略类,避免了条件判断逻辑的硬编码,同时让新增算法变得非常简单,只需要新增一个策略类,而不需要修改现有的代码。
如何使用:
public interface Strategy {
int doOperation(int num1, int num2);
}
public class OperationAdd implements Strategy {
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2) {
return strategy.doOperation(num1, num2);
}
}
Strategy
是策略接口,定义了一个通用的操作方法doOperation()
。OperationAdd
和OperationSubtract
分别实现了不同的策略(加法和减法)。Context
类持有一个Strategy
对象,客户端可以根据需要动态选择策略并执行相应的算法。
4. 观察者模式(Observer Pattern)
目的:
- 实现一对多的依赖关系:观察者模式的核心在于,当一个对象的状态发生变化时,它会自动通知所有依赖于它的对象,从而实现松耦合的事件驱动系统。
解决问题:
- 耦合度过高和手动更新的不便:在某些系统中,多个对象依赖于同一数据或状态。当数据或状态发生变化时,必须通知所有依赖者进行更新。如果通过手动调用来更新这些依赖者,不仅增加了代码的复杂性,还可能导致依赖者与数据源耦合过高。观察者模式通过自动化这个过程,确保依赖者在状态改变时被自动通知和更新,减少了手动维护和对象间的耦合。
如何使用:
public class Subject {
private List<Observer> observers = new ArrayList<>();
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
notifyAllObservers();
}
public void attach(Observer observer) {
observers.add(observer);
}
public void notifyAllObservers() {
for (Observer observer : observers) {
observer.update();
}
}
}
public abstract class Observer {
protected Subject subject;
public abstract void update();
}
Subject
类维护一个state
状态,当状态改变时,它通知所有依赖该状态的观察者(即Observer
)。attach()
方法用于将观察者添加到通知列表中。- 当状态发生改变时,
notifyAllObservers()
会遍历所有观察者并调用它们的update()
方法。
5.责任链模式(Chain of Responsibility Pattern)
目的:
- 动态传递和处理请求:责任链模式的目的是将多个处理者连接成一条链,当有请求到来时,沿着这条链依次传递,直到有处理者处理请求为止。
解决问题:
- 请求处理的顺序和灵活性:在某些系统中,比如日志系统、权限验证系统、表单验证等,处理请求的逻辑可能涉及多个步骤或多个对象。如果客户端代码必须明确指定每个对象处理的顺序或依次调用各个处理者,不仅代码臃肿,而且不具备扩展性。责任链模式通过将这些处理者串联成一个链,客户端只需发出请求,而不关心谁在处理或请求如何被传递。每个处理者只需要决定是否处理请求,如果不处理则传递给下一个处理者,从而提高了系统的灵活性和可扩展性。
如何使用:
public abstract class Handler {
protected Handler nextHandler;
public void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
public abstract void handleRequest(String request);
}
// 具体处理者A
public class ConcreteHandlerA extends Handler {
@Override
public void handleRequest(String request) {
if (request.equals("A")) {
System.out.println("Handler A handled the request.");
} else if (nextHandler != null) {
nextHandler.handleRequest(request);
}
}
}
// 具体处理者B
public class ConcreteHandlerB extends Handler {
@Override
public void handleRequest(String request) {
if (request.equals("B")) {
System.out.println("Handler B handled the request.");
} else if (nextHandler != null) {
nextHandler.handleRequest(request);
}
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Handler handlerA = new ConcreteHandlerA();
Handler handlerB = new ConcreteHandlerB();
handlerA.setNextHandler(handlerB);
handlerA.handleRequest("B"); // 输出:Handler B handled the request.
}
}
Handler
是抽象类,定义了一个指向下一个处理者的引用nextHandler
和一个处理请求的抽象方法handleRequest()
。ConcreteHandlerA
和ConcreteHandlerB
是具体的处理者类,当请求符合它们的处理条件时,它们会处理请求,否则将请求传递给下一个处理者。- 通过设置处理链,客户端代码可以将请求依次传递给处理链上的每个处理者,直到有处理者处理该请求。
四、五种设计模式横向对比
设计模式 | 目的 | 结构复杂性 | 灵活性 | 典型场景 | 优点 | 缺点 |
---|---|---|---|---|---|---|
单例模式 | 确保某个类只有一个实例,提供全局访问点 | 简单 | 灵活性较低 | 日志管理、配置管理、数据库连接池等 | 控制实例数量,减少资源浪费;提供全局访问点 | 状态共享问题,线程安全需特别关注 |
工厂模式 | 将对象的创建过程与使用分离,简化对象的创建过程 | 中等 | 较高 | 创建复杂对象或动态选择对象类型,如数据库连接、图形界面控件等 | 解耦对象创建和使用;简化复杂对象的创建;便于扩展新的对象类型 | 随着创建逻辑的复杂性增加,工厂模式可能变得难以维护 |
策略模式 | 动态选择算法或行为,避免复杂的条件判断逻辑 | 中等 | 很高 | 动态切换算法、行为如支付方式选择、不同排序算法的选择等 | 便于动态选择算法;避免条件分支;扩展新策略时无需修改已有代码 | 类数量增多,策略切换的管理可能变得复杂 |
观察者模式 | 定义一对多依赖,当对象状态变化时通知所有依赖它的对象 | 中等 | 高 | 事件驱动系统、消息推送系统、股票价格更新、GUI事件处理等 | 解耦对象间的依赖关系;支持事件通知机制;便于实现分布式消息系统 | 通知链复杂,异步处理时需考虑顺序和一致性 |
责任链模式 | 将请求沿着处理链传递,直至有对象处理它 | 中等到复杂 | 高 | 处理多个处理器顺序处理请求的场景,如日志过滤器、权限检查、表单验证等 | 解耦请求发送者与多个处理者;动态改变链中处理者的顺序和组合 | 链过长时性能开销较大,链中处理者难以明确哪些对象处理了请求 |
1. 结构复杂性对比
- 单例模式:最简单,只需要一个类并控制实例化过程即可。
- 工厂模式:结构中等,通过一个工厂类封装对象的创建过程,较为直观。
- 策略模式:中等复杂度,需要定义多个策略类以及一个上下文类来动态选择策略。
- 观察者模式:结构中等,涉及一个发布者和多个订阅者,需要管理订阅和通知机制。
- 责任链模式:中等到复杂,涉及多个处理者类,需要管理处理链的创建和传递过程。
2. 典型场景对比
- 单例模式:用于管理全局状态或共享资源的场景,如配置管理、数据库连接池、日志系统。
- 工厂模式:适用于创建复杂对象,尤其是对象创建逻辑多变、需要动态选择对象类型的场景,如数据访问层中的DAO对象创建。
- 策略模式:适用于需要根据条件动态选择算法或行为的场景,如支付系统中不同支付方式的选择,数据加密解密的策略选择等。
- 观察者模式:适用于事件驱动系统、实时数据通知系统、GUI事件处理、消息推送系统等,如股票行情通知系统、社交媒体的消息通知等。
- 责任链模式:适用于多个处理者顺序处理请求的场景,如日志过滤器、权限验证系统、表单验证系统、请求处理器链。