技术栈java面试JAVA设计模式面试题目hot
mengnankkzhouController
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("密码错误"); }
String token = JwtUtil.createToken(user.getId().toString(), 10 * 60 * 1000);
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.你在工作中是如何进行参数非空校验的?
在工作中,我会根据具体场景,在 三个层次 对参数进行非空校验:
- 前端校验:前端通过 JS 或组件校验框架(如 Element Plus 表单校验、AntD Rule)来做第一层参数合法性校验,提升用户体验。
- 后端控制层校验(推荐):使用 Spring 的
javax.validation
注解(如 @NotBlank
、@NotNull
、@Size
)结合 @Valid
实现自动参数校验,统一异常处理返回提示。
- 业务逻辑校验(服务层):对关键参数进行业务校验(如账号是否存在、密码是否为空、两个字段不能同时为空等),保证逻辑正确性。
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;
@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(); } } } 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; logger.info("创建图片代理:{}", filename); } @Override public void display() { if (realImage == null) { 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"); logger.info("显示第一张图片..."); image1.display(); 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); } }
|
使用:
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) { Document adminDocument = new DocumentProtectionProxy("sensitive.txt", "admin"); try { logger.info(adminDocument.access()); } catch (SecurityException e) { logger.error("admin 访问被拒绝", e); } 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();
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; } }
|
java扩展
1.同步和异步的区别是什么?
同步(Synchronous)和异步(Asynchronous) 是两种不同的任务执行方式,主要区别在于任务的执行是否需要等待其他任务完成。
同步的话是需要等待任务完成之后,收到返回的确认请求的时候再进行其他的
异步的话,就是不需要等待,直接进行下一步任务