JAVA设计模式面试题目hot

Controller

1.写一个简单的登录接口的Controller,包含用户ID和密码字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@RestController
@RequestMapping("/api/auth")
public class AuthController {

@Autowired
private UserService userService;

@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
User user = userService.findByUserId(request.getUserId());
if (user == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("用户不存在");
}

if (!user.getPassword().equals(request.getPassword())) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("密码错误");
}

// 模拟生成 JWT(可替换为真实生成)
String token = JwtUtil.createToken(user.getId().toString(), 10 * 60 * 1000); // 10分钟有效

Map<String, Object> response = new HashMap<>();
response.put("accessToken", token);
response.put("userId", user.getId());
response.put("username", user.getUsername());

return ResponseEntity.ok(response);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class UserService {
public User findByUserId(String userId) {
// 模拟查库
if ("admin".equals(userId)) {
User user = new User();
user.setId(1L);
user.setUserId("admin");
user.setUsername("管理员");
user.setPassword("123456"); // 明文密码仅用于演示
return user;
}
return null;
}
}

2.对于POST请求,你是直接用参数接收,还是封装成对象接收?

封装成对象来接受。

在开发中,对于 POST 请求,我更倾向于使用 @RequestBody 将参数封装成对象。这种方式更清晰、可维护,也方便进行参数校验和自动生成 Swagger 文档。如果参数较少、是简单的表单提交,偶尔也可以用 @RequestParam

1
2
3
4
5
6
7
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
String userId = request.getUserId();
String password = request.getPassword();
// 登录逻辑...
}

1
2
3
4
5
6
@Data
public class LoginRequest {
private String userId;
private String password;
}

封装成类,参数多的时候,不宜混乱,方便扩展,新增字段的时候,不用改方法签名,支持验证注解,如 @NotBlank@Size 直接加在类字段上。更加适合Swagger自动文档,自动识别请求结构。

不封装的话,参数签名越来越长,而且无法处理 application/json 请求。不能队单个字段加验证规则。

3.你在工作中是如何进行参数非空校验的?

在工作中,我会根据具体场景,在 三个层次 对参数进行非空校验:

  1. 前端校验:前端通过 JS 或组件校验框架(如 Element Plus 表单校验、AntD Rule)来做第一层参数合法性校验,提升用户体验。
  2. 后端控制层校验(推荐):使用 Spring 的 javax.validation 注解(如 @NotBlank@NotNull@Size)结合 @Valid 实现自动参数校验,统一异常处理返回提示。
  3. 业务逻辑校验(服务层):对关键参数进行业务校验(如账号是否存在、密码是否为空、两个字段不能同时为空等),保证逻辑正确性。

controller层

1
2
3
4
5
6
@PostMapping("/login")
public ResponseEntity<?> login(@Valid @RequestBody LoginRequest request) {
// 如果参数校验失败,会自动抛出异常
return authService.login(request);
}

DTO+注解

1
2
3
4
5
6
7
8
9
10
@Data
public class LoginRequest {

@NotBlank(message = "用户ID不能为空")
private String userId;

@NotBlank(message = "密码不能为空")
private String password;
}

全局异常处理:

1
2
3
4
5
6
7
8
9
10
@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleValidationException(MethodArgumentNotValidException ex) {
String msg = ex.getBindingResult().getFieldError().getDefaultMessage();
return ResponseEntity.badRequest().body(Map.of("error", msg));
}
}

还封装了统一的 BaseRequest 类 + 参数校验通用注解,配合全局异常返回标准化的错误结构,做到接口友好、开发高效。

BaseRequest

1
2
3
4
5
6
7
8
9
10
11
12
@Data
public class BaseRequest {
@NotNull(message = "请求时间戳不能为空")
private Long timestamp;

@NotBlank(message = "请求来源不能为空")
private String source; // web / app / admin 等

@NotBlank(message = "签名不能为空")
private String sign; // 用于简单签名校验
}

实际请求

1
2
3
4
5
6
7
8
9
10
@Data
public class LoginRequest extends BaseRequest {

@NotBlank(message = "用户ID不能为空")
private String userId;

@NotBlank(message = "密码不能为空")
private String password;
}

Controller 中使用 @Valid 接收参数

1
2
3
4
5
6
@PostMapping("/login")
public ResponseEntity<?> login(@Valid @RequestBody LoginRequest request) {
// 校验通过,执行业务逻辑
return authService.login(request);
}

全局异常处理返回统一格式(如 Result<T>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
private Integer code;
private String message;
private T data;

public static <T> Result<T> success(T data) {
return new Result<>(200, "success", data);
}

public static Result<?> error(String message) {
return new Result<>(400, message, null);
}
}

参数校验统一异常处理(@ControllerAdvice

1
2
3
4
5
6
7
8
9
10
11
12
@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Result<?>> handleValidation(MethodArgumentNotValidException ex) {
String msg = ex.getBindingResult().getFieldError().getDefaultMessage();
return ResponseEntity.badRequest().body(Result.error(msg));
}

// 其他通用异常处理
}

我们项目里统一封装了请求基类 BaseRequest,每个接口请求都包含 timestamp、source、sign 等字段,同时结合 Spring 的 @Valid 和自定义的通用注解进行字段校验,配合统一异常处理类,返回格式统一成 Result<T>。这样接口文档清晰、调试方便、前后端联调效率高,开发体验提升很多。

设计模式

分类 模式 作用 示例关键点
创建型 单例模式 保证全局只有一个实例 Spring 中的 Bean 默认就是单例
创建型 工厂模式 统一创建对象 数据库连接、策略类
结构型 代理模式 给目标对象增加额外功能 Spring AOP、事务、日志
结构型 装饰器模式 动态扩展对象功能 IO 流、过滤器链
行为型 策略模式 可切换的行为算法 支付、消息推送
行为型 观察者模式 发布/订阅事件通知 MQ、事件监听
行为型 模板方法 固定流程 + 可变步骤 抽象 Controller、登录流程
行为型 责任链模式 多个处理器依次处理 过滤器链、权限认证
行为型 状态模式 对象状态驱动行为 订单、任务状态
行为型 命令模式 把操作封装成对象 任务撤销、限流操作命令

单例模式

确保一个类在整个应用中 只有一个实例,并提供一个全局访问点。

  • Redis 工具类、线程池、连接池、配置类等
  • Spring 默认是单例 Bean(IOC 控制)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton {
private static volatile Singleton instance;

private Singleton() {}

public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton(); // 防止指令重排
}
}
}
return instance;
}
}

基于volatile和synchronized

volatile:防止 JVM 指令重排序,确保实例初始化完成后再赋值。

双重校验:第一次 if 避免每次加锁,第二次 if 避免并发重复创建。

比如Calendar:

1
Calendar calendar = Calendar.getInstance();

比如 Logger:

1
Logger logger = Logger.getLogger(MyClass.class.getName());

单例的实现方式:

饿汉式(静态初始化):类加载时直接创建实例,线程安全。

1
2
3
4
5
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return instance; }
}

简单可靠,类加载即初始化,无线程安全问题。不管是否使用都会创建实例,可能浪费内存。

懒汉式(同步方法):首次使用时创建实例,用 synchronized 保证线程安全。

1
2
3
4
5
6
7
8
9
10
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

延迟初始化,节省内存。

synchronized 锁在方法级别,并发访问时性能较低。

双重检查锁(DCL):用 Volatile + 双重判空 + synchronized,兼顾性能和安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {
private volatile static Singleton instance; // 防止指令重排序
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一重判空
synchronized (Singleton.class) { // 锁类
if (instance == null) { // 第二重判空
instance = new Singleton(); // 非原子操作,需Volatile
}
}
}
return instance;
}
}

仅在第一次创建时加锁,性能高,JDK1.5 后安全。

instance 必须用volatile修饰,避免多线程下初始化顺序问题。

静态内部类(推荐!):利用类加载机制,内部类加载时创建实例,线程安全。

1
2
3
4
5
6
7
8
9
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

延迟初始化,代码简洁,JVM 保证线程安全,无反射破坏问题(需额外处理)。

枚举方式:枚举类天然单例,防反射、防序列化破坏。

1
2
3
4
5
public enum Singleton {
INSTANCE;
// 可添加方法
public void doSomething() {}
}

1.说下Spring 的ApplicationContext是单例模式”“策略模式在排序算法中的应用”

ApplicationContext 本质上采用了单例模式来保证 Spring 容器中 Bean 的唯一性和全局访问能力

Spring Boot 启动时会初始化一个 ApplicationContext(如 AnnotationConfigApplicationContext),作为IoC 容器的核心上下文

这个容器对象在整个应用中只创建一次(单例),所有组件(Controller、Service、Repository)都从这个容器中获取 Bean 实例。

避免了重复初始化 Bean 的性能开销,也方便了依赖管理、统一配置、事件发布等功能的实现。

1
2
3
ApplicationContext context = SpringApplication.run(MyApp.class, args);
MyService service = context.getBean(MyService.class);

开发中我们只需注入一次上下文或通过 @Autowired 注解获取 Bean,即可全局复用。

Spring 默认的 Bean 是单例的(@Scope("singleton")),这与 ApplicationContext 单例模型相辅相成,进一步保证了资源一致性与管理效率。

工厂模式

根据不同类型 生成不同实现类实例,解耦对象创建过程。

  • 根据类型创建支付处理器、登录方式、导出类型等

  • 创建支付方式(支付宝、微信)

    创建导出服务(Excel、PDF)

    登录策略、文件解析等类型多变组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.mengnankk.designpattern;

// 工厂类
public class ExportFactory {
public static ExportService getExport(String type) {
if ("excel".equalsIgnoreCase(type)) {
return new ExcelExportService();
} else {
throw new RuntimeException("不支持的类型");
}
}
}


interface ExportService {
void export();
}


class ExcelExportService implements ExportService {
public void export() {
System.out.println("导出Excel");
}
}

结合接口隔离 + 枚举注册

通过工厂类封装创建逻辑

返回的是接口类型,利于扩展和解耦

结合枚举或配置可实现注册式工厂

策略模式

定义一系列算法(策略),并使它们可以相互替换。将行为与选择解耦。定义算法族,封装后可相互替换,客户端根据条件选择算法。

多种支付方式、登录方式、审核策略等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.mengnankk.designpattern;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import java.util.Map;

@Service
public class PayContext {
@Autowired
private Map<String,PayStrategy> paymap;

public void dopay(String type){
PayStrategy strategy = paymap.get(type);
if (strategy!=null) strategy.pay();
}
}
interface PayStrategy{
void pay();
}
@Component("aliPay")
class AliPay implements PayStrategy {
public void pay() {
System.out.println("支付宝支付");
}
}
@Component("wechatPay")
class WeChatPay implements PayStrategy {
public void pay() { System.out.println("微信支付"); }
}



利用 Spring 容器自动注入所有策略 Bean

根据 key 获取策略执行对应逻辑,避免写死 if-else 或 switch

和工厂模式的最大区别是:策略封装行为,工厂封装创建

面试题目:设计模式场景题:比如现在有支付宝、微信、各大银行卡等支付方式,最坏的情况是写了20多个if-else判断是用户选择哪种支付方式,耦合度很高,如果叫你优化,你怎么想?

我们可以使用策略模式,先封装一个支付的接口,然后把每个支付的方式分装成类,然后用一个map集合或者是工厂类来让用户选择具体的是哪个支付方式,这样就避免了大量使用if-else。

需要一个接口,然后具体的实现类,然后一个工厂类,使用map简化策略选择。还有一个策略的上下文类

然后客户端调用上下文类(里面是工厂实例),然后选择支付金额。这里的支付金额要使用BigDecimal

因为 double二进制浮点数,在进行运算时会出现精度误差

使用BigDecimal 基于字符串表示的小数类,可以实现精确计算,避免金额误差,防止“丢钱”或“多扣”的风险。

代理模式

在不修改目标类的情况下,增强其功能(如日志、权限、事务等)

可以解决紧耦合代码

日志、权限、事务控制、接口限流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.mengnankk.designpattern;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.lang.annotation.*;

@Aspect
@Component
public class LogAspect {

@Before("@annotation(com.mengnankk.designpattern.Log)") // 指定注解全路径
public void logBefore(JoinPoint point) {
System.out.println("调用方法:" + point.getSignature().getName());
}
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface Log {
String value() default "";
}


这是动态代理,AOP,还有静态代理

AOP 本质是动态代理(JDK 或 CGLIB)

利用切点表达式 @annotation 定位目标方法

增强功能通过切面类编织

它为另一个对象提供一个代理以控制对该对象的访问。代理对象在客户端和目标对象之间起到中介的作用,客户端通过代理对象来访问目标对象,而代理对象可以在不改变目标对象代码的情况下,增加额外的功能,

  • Subject(主题/抽象主题): 定义了 RealSubject 和 Proxy 的共同接口,这样在任何可以使用 RealSubject 的地方都可以使用 Proxy。
  • RealSubject(真实主题): 定义了代理所代表的真实对象,也就是实际执行业务逻辑的对象。
  • Proxy(代理): 持有 RealSubject 的引用,控制对 RealSubject 的访问。 它可以在访问 RealSubject 前后进行一些额外的操作。

分类:

  • 远程代理(Remote Proxy): 为位于不同地址空间的对象提供局部代表。 远程代理隐藏了对象位于远程机器上的事实。
  • 虚拟代理(Virtual Proxy): 根据需要创建开销较大的对象。 虚拟代理用于延迟 RealSubject 的创建,只有在真正需要的时候才创建它。
  • 保护代理(Protection Proxy): 控制对 RealSubject 的访问。 保护代理用于在客户端访问 RealSubject 之前检查访问权限。
  • 智能引用代理(Smart Reference Proxy): 当主题被引用时,执行一些额外的操作,例如,记录对象的引用次数。

实例:支付环境

真实主题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class RemotePaymentService extends UnicastRemoteObject implements PaymentService {
private static final Logger logger = LoggerFactory.getLogger(RemotePaymentService.class);
public RemotePaymentService() throws RemoteException {
super();
}
@Override
public String pay(String userId, BigDecimal amount) {
// 模拟远程支付逻辑
logger.info("开始远程支付,用户ID:{},金额:{}", userId, amount);
// 实际的支付逻辑会涉及调用第三方支付接口、数据库操作等
try{
Thread.sleep(1000); //模拟远程调用耗时
logger.info("远程支付完成,用户ID:{},金额:{}", userId, amount);
} catch (InterruptedException e){
logger.error("远程支付异常", e);
return "支付失败";
}
return "支付成功";
}
}

代理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class PaymentServiceProxy implements PaymentService {
private static final Logger logger = LoggerFactory.getLogger(PaymentServiceProxy.class);
private final String remoteServiceUrl;
private PaymentService realPaymentService;
public PaymentServiceProxy(String remoteServiceUrl) {
this.remoteServiceUrl = remoteServiceUrl;
this.realPaymentService = null; // 延迟加载,只有在真正使用时才连接远程服务
}
private PaymentService getRealPaymentService() throws RemoteException, NotBoundException, MalformedURLException {
if (realPaymentService == null) {
// 延迟加载:第一次调用时才连接远程服务
logger.info("连接远程支付服务:{}", remoteServiceUrl);
realPaymentService = (PaymentService) Naming.lookup(remoteServiceUrl);
logger.info("成功连接远程支付服务:{}", remoteServiceUrl);
}
return realPaymentService;
}
@Override
public String pay(String userId, BigDecimal amount) {
try {
return getRealPaymentService().pay(userId, amount);
} catch (RemoteException | NotBoundException | MalformedURLException e) {
logger.error("远程支付失败", e);
return "远程支付失败,请稍后重试";
}
}
}

虚拟代理:

图片服务器需要加载大量的图片,如果一次性加载所有图片,会占用大量的内存资源,导致应用启动缓慢甚至崩溃。使用虚拟代理可以延迟图片的加载,只有当用户需要查看图片时才真正加载。

RealImage:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class RealImage implements Image {
private static final Logger logger = LoggerFactory.getLogger(RealImage.class);
private final String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk(); // 构造时加载图片
}
private void loadFromDisk() {
// 模拟从磁盘加载图片的过程 (实际情况会读取文件)
logger.info("从磁盘加载图片:{}", filename);
try {
Thread.sleep(2000); // 模拟耗时加载
} catch (InterruptedException e) {
logger.error("加载图片异常", e);
}
logger.info("图片加载完成:{}", filename);
}
@Override
public void display() {
logger.info("显示图片:{}", filename);
}
}

代理:VirtualImageProxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class VirtualImageProxy implements Image {
private static final Logger logger = LoggerFactory.getLogger(VirtualImageProxy.class);
private RealImage realImage;
private final String filename;
public VirtualImageProxy(String filename) {
this.filename = filename;
this.realImage = null; // 初始时 RealImage 为空
logger.info("创建图片代理:{}", filename);
}
@Override
public void display() {
if (realImage == null) {
// 延迟加载:只有在第一次显示图片时才创建 RealImage
logger.info("第一次显示图片,开始创建 RealImage:{}", filename);
realImage = new RealImage(filename);
}
realImage.display();
}
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Client {
private static final Logger logger = LoggerFactory.getLogger(Client.class);
public static void main(String[] args) {
Image image1 = new VirtualImageProxy("image1.jpg");
Image image2 = new VirtualImageProxy("image2.png");
// 此时 RealImage 还没有被创建
// 第一次显示图片,才会创建 RealImage
logger.info("显示第一张图片...");
image1.display();
// 第二次显示图片,直接使用已经创建好的 RealImage
logger.info("再次显示第一张图片...");
image1.display();
logger.info("显示第二张图片...");
image2.display();
}
}

虚拟代理实现了图片的延迟加载,只有在需要显示图片时才创建 RealImage 对象,减少了初始内存占用,提高了应用的启动速度。

保护代理:需要控制用户对文档的访问权限,防止未经授权的访问。

SensitiveDocument

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class SensitiveDocument implements Document {
private static final Logger logger = LoggerFactory.getLogger(SensitiveDocument.class);
private final String filename;
public SensitiveDocument(String filename) {
this.filename = filename;
load(); // 构造时加载文档
}
@Override
public void load() {
// 模拟从磁盘加载敏感文档的过程
logger.info("从磁盘加载敏感文档:{}", filename);
try {
Thread.sleep(1500); // 模拟耗时加载
} catch (InterruptedException e) {
logger.error("加载文档异常",e);
}
logger.info("敏感文档加载完成:{}", filename);
}
@Override
public String access() {
return "访问敏感文档内容:" + filename;
}
}

代理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class DocumentProtectionProxy implements Document {
private static final Logger logger = LoggerFactory.getLogger(DocumentProtectionProxy.class);
private Document realDocument;
private final String filename;
private final String userRole; // 用户的角色
public DocumentProtectionProxy(String filename, String userRole) {
this.filename = filename;
this.userRole = userRole;
this.realDocument = null; // 延迟加载
}
@Override
public void load() {
// 因为可能需要加载文档,所以这里也进行权限检查
if (hasPermission()) {
if (realDocument == null) {
realDocument = new SensitiveDocument(filename); // 第一次访问时加载真实文档
}
realDocument.load();
} else {
logger.warn("用户 {} 没有权限加载文档:{}", userRole, filename);
throw new SecurityException("无权访问该文档");
}
}
@Override
public String access() {
if (hasPermission()) {
if (realDocument == null) {
realDocument = new SensitiveDocument(filename); // 第一次访问时加载真实文档
}
return realDocument.access();
} else {
logger.warn("用户 {} 没有权限访问文档:{}", userRole, filename);
throw new SecurityException("无权访问该文档");
}
}
private boolean hasPermission() {
// 模拟权限检查,实际情况会根据用户角色、权限列表等进行判断
return "admin".equals(userRole); // 只有 admin 角色才能访问敏感文档
}
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Client {
private static final Logger logger = LoggerFactory.getLogger(Client.class);
public static void main(String[] args) {
// admin 用户有权限
Document adminDocument = new DocumentProtectionProxy("sensitive.txt", "admin");
try {
logger.info(adminDocument.access()); // 输出文档内容
} catch (SecurityException e) {
logger.error("admin 访问被拒绝", e);
}
// user 用户没有权限
Document userDocument = new DocumentProtectionProxy("sensitive.txt", "user");
try {
logger.info(userDocument.access()); // 抛出异常
} catch (SecurityException e) {
logger.error("user 访问被拒绝", e);
}
}
}

修饰器模式

在不修改原始对象的情况下,动态增强其功能(与代理很像,区别是包装对象)

请求/响应增强处理、日志追加处理、加密/解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.mengnankk.designpattern;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter("/*")
public class RequestLogFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) {
// 初始化逻辑(可选)
}

@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
System.out.println("记录请求日志");
chain.doFilter(req, res);
}

@Override
public void destroy() {
// 资源释放逻辑(可选)
}
}

类似代理,但更偏向“嵌套增强”

典型用法是 Filter、Interceptor,形成处理链

可以组合多个修饰器链式增强

适配器模式

配器模式像是“插头转换器”,让两个原本不兼容的接口连接上。

  • 类适配器:通过继承适配源类,实现目标接口(Java 单继承限制,使用较少)。
  • 对象适配器:通过组合源类实例,实现目标接口(更常用)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface MediaPlayer {
void play(String type, String fileName);
}

class OldPlayer {
public void playAudio(String fileName) {
System.out.println("播放音频:" + fileName);
}
}

class AudioAdapter implements MediaPlayer {
private OldPlayer oldPlayer = new OldPlayer();

@Override
public void play(String type, String fileName) {
if ("mp3".equalsIgnoreCase(type)) {
oldPlayer.playAudio(fileName);
}
}
}

JDBC 驱动适配不同数据库、Spring MVC 的适配器处理不同请求。

维度 代理模式 适配器模式
目的 控制访问目标对象,添加附加逻辑 接口不兼容时进行适配
原始类 与代理类实现同一接口 与目标接口无关,通过包装“适配”
使用时机 不想或不能直接访问目标对象,增加功能(如远程调用、权限) 复用已有类但其接口不符合当前系统要求
是否增强 是,可以添加前置/后置逻辑 否,仅做接口适配

模板方法模式

模板方法,比如说有三个模板方法A、B、C,第一个子类我想让他实现ABC这么执行,第二个子类我想让他实现ACB,这个怎么实现?(使用“钩子方法”,抽离一个方法返回true、false,true就ABC,false就ACB)

模板方法模式 + 钩子方法(Hook)自定义流程控制

在抽象类中定义整体流程 A → B → C,然后用钩子方法来控制流程顺序

  • 抽象类中定义 template() 模板方法
  • A()C() 是固定流程
  • B() 是可选的
  • 使用 shouldDoB() 钩子方法(返回 true/false)来决定执行顺序,是 ABC 还是 ACB

定义抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public abstract class Template {

// 模板方法
public final void execute() {
A();
if (shouldDoBFirst()) {
B();
C();
} else {
C();
B();
}
}

protected abstract void A();
protected abstract void B();
protected abstract void C();

// 钩子方法,默认返回 true,即执行 ABC
protected boolean shouldDoBFirst() {
return true;
}
}

子类去继承这个抽象类,然后重写父类的方法shouldDoBFirst,按照需求重写。

1
2
3
4
5
6
7
8
9
public class FirstChild extends Template {
protected void A() { System.out.println("A"); }
protected void B() { System.out.println("B"); }
protected void C() { System.out.println("C"); }
protected boolean shouldDoBFirst() {
return true; // 默认就是 ABC
}
}

java扩展

1.同步和异步的区别是什么?

同步(Synchronous)和异步(Asynchronous) 是两种不同的任务执行方式,主要区别在于任务的执行是否需要等待其他任务完成。

同步的话是需要等待任务完成之后,收到返回的确认请求的时候再进行其他的

异步的话,就是不需要等待,直接进行下一步任务