设计模式
设计模式
设计模式是软件开发中常见问题的通用解决方案,用于指导代码的组织和结构。还在学习当中,只记录一些项目中常用的一些设计模式!
单例模式 (Singleton)
核心思想
单例模式确保一个类在整个应用程序的生命周期中只有一个实例存在,并提供一个全局访问点来获取这个实例。
为什么需要单例?
在实际开发中,有些对象我们只需要一个,例如:
- 线程池:频繁创建销毁线程非常消耗资源
- 数据库连接池:维护一个全局共享的连接池
- 配置管理器:应用的配置信息只需要加载一次
- 日志对象:全局共享一个日志记录器
如果这些对象被频繁创建,不仅浪费内存,还可能导致状态不一致的问题。
实现方式
1. 饿汉式 - 静态常量
这是最简单的实现方式,在类加载时就完成实例化。
public class Singleton {
// 类加载时就创建实例
private static final Singleton INSTANCE = new Singleton();
// 私有构造方法,防止外部 new
private Singleton() {
// 防止通过反射破坏单例
if (INSTANCE != null) {
throw new RuntimeException("不允许创建多个实例");
}
}
public static Singleton getInstance() {
return INSTANCE;
}
}优点:
- 写法简单,类加载时完成初始化,避免了线程同步问题
- JVM 保证线程安全
缺点:
- 类加载时就创建实例,如果该实例从未被使用,会造成内存浪费
- 无法实现懒加载
2. 饿汉式 - 静态代码块
与静态常量类似,但可以在静态代码块中处理更复杂的初始化逻辑。
public class Singleton {
private static final Singleton INSTANCE;
// 静态代码块,在类加载时执行
static {
try {
// 可以在这里进行复杂的初始化操作
// 比如读取配置文件、初始化连接等
INSTANCE = new Singleton();
} catch (Exception e) {
throw new RuntimeException("单例初始化失败", e);
}
}
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}适用场景:当单例对象的初始化需要处理异常或依赖外部资源时。
3. 懒汉式 - 双重检查锁 (DCL)
只有在第一次调用 getInstance() 时才创建实例,实现懒加载。
public class Singleton {
// volatile 关键字非常关键!
// 1. 保证可见性:一个线程修改后,其他线程立即可见
// 2. 禁止指令重排序:防止 instance 在构造函数执行完毕前被赋值
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?
instance = new Singleton() 这行代码实际上分为三步:
- 分配内存空间
- 调用构造函数初始化对象
- 将 instance 指向分配的内存
JVM 可能会对步骤 2 和 3 进行指令重排序,变成 1→3→2。如果线程 A 执行到 3 还没执行 2,此时线程 B 进入第一次检查,发现 instance 不为 null,直接返回一个未初始化完成的对象,导致程序出错。
4. 静态内部类 (Holder Pattern) 【推荐】
结合了懒加载和线程安全,是目前最推荐的实现方式。
public class Singleton {
private Singleton() {}
// 静态内部类,只有在被调用时才会加载
private static class SingletonHolder {
// 类加载机制保证线程安全
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
// 调用此方法时,才会导致 SingletonHolder 类被加载
return SingletonHolder.INSTANCE;
}
}为什么这种方式最优?
- 懒加载:只有调用
getInstance()时,SingletonHolder类才会被加载,此时才创建实例 - 线程安全:类加载过程由 JVM 保证线程安全,不需要显式同步
- 性能好:没有同步开销
5. 枚举单例 【最安全】
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("执行业务逻辑");
}
}
// 使用
Singleton.INSTANCE.doSomething();优点:
- 天然防止反射攻击和序列化破坏
- 写法最简洁
工厂模式 (Factory)
核心思想
将对象的创建逻辑与使用逻辑分离。客户端不需要知道具体类的实现细节,只需要告诉工厂"我需要什么类型的对象"。
为什么需要工厂模式?
假设你有一个支付系统,需要支持支付宝、微信、银联等多种支付方式:
不使用工厂模式:
// 在业务代码中直接创建对象
if ("alipay".equals(type)) {
payment = new AlipayPayment();
} else if ("wechat".equals(type)) {
payment = new WechatPayment();
} else if ("union".equals(type)) {
payment = new UnionPayment();
}问题:
- 每次新增支付方式,都要修改业务代码
- 违反开闭原则(对扩展开放,对修改关闭)
- 创建逻辑散落在各处,难以维护
简单工厂实现
// 1. 定义支付接口
public interface Payment {
void pay(BigDecimal amount);
}
// 2. 具体实现类
public class AlipayPayment implements Payment {
@Override
public void pay(BigDecimal amount) {
System.out.println("支付宝支付: " + amount);
}
}
public class WechatPayment implements Payment {
@Override
public void pay(BigDecimal amount) {
System.out.println("微信支付: " + amount);
}
}
// 3. 工厂类
public class PaymentFactory {
private static final Map<String, Payment> paymentMap = new HashMap<>();
// 使用静态代码块预加载,也可以结合 Spring 自动注入
static {
paymentMap.put("alipay", new AlipayPayment());
paymentMap.put("wechat", new WechatPayment());
}
public static Payment getPayment(String type) {
Payment payment = paymentMap.get(type);
if (payment == null) {
throw new IllegalArgumentException("不支持的支付类型: " + type);
}
return payment;
}
}
// 4. 使用
Payment payment = PaymentFactory.getPayment("alipay");
payment.pay(new BigDecimal("100.00"));结合 Spring 的工厂模式
在实际项目中,可以利用 Spring 容器自动管理:
@Component
public class PaymentFactory {
@Autowired
private Map<String, Payment> paymentMap;
// Spring 会自动将所有 Payment 实现类注入到这个 Map
// key 是 Bean 名称,value 是 Bean 实例
public Payment getPayment(String type) {
return paymentMap.get(type);
}
}策略模式 (Strategy)
核心思想
定义一系列算法,将每个算法封装成独立的类,使它们可以互相替换。让算法的变化独立于使用算法的客户端。
与工厂模式的区别
- 工厂模式关注的是对象的创建
- 策略模式关注的是算法的选择和执行
实际项目中,这两种模式经常配合使用:工厂负责创建策略对象,策略负责执行具体逻辑。
完整示例
// 1. 策略接口
public interface DiscountStrategy {
BigDecimal calculate(BigDecimal originalPrice);
}
// 2. 具体策略:普通会员 - 9折
@Component("normal")
public class NormalDiscountStrategy implements DiscountStrategy {
@Override
public BigDecimal calculate(BigDecimal originalPrice) {
return originalPrice.multiply(new BigDecimal("0.9"));
}
}
// 3. 具体策略:VIP会员 - 8折
@Component("vip")
public class VipDiscountStrategy implements DiscountStrategy {
@Override
public BigDecimal calculate(BigDecimal originalPrice) {
return originalPrice.multiply(new BigDecimal("0.8"));
}
}
// 4. 具体策略:超级VIP - 7折
@Component("svip")
public class SvipDiscountStrategy implements DiscountStrategy {
@Override
public BigDecimal calculate(BigDecimal originalPrice) {
return originalPrice.multiply(new BigDecimal("0.7"));
}
}
// 5. 策略上下文(结合工厂)
@Component
public class DiscountContext {
@Autowired
private Map<String, DiscountStrategy> strategyMap;
public BigDecimal executeDiscount(String memberLevel, BigDecimal price) {
DiscountStrategy strategy = strategyMap.get(memberLevel);
if (strategy == null) {
throw new IllegalArgumentException("未知的会员等级");
}
return strategy.calculate(price);
}
}优点:
- 消除大量 if-else 或 switch-case
- 新增策略只需添加新类,无需修改原有代码
- 策略之间完全解耦,易于测试
代理模式 (Proxy)
核心思想
为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用,可以在调用目标方法前后添加额外逻辑。
应用场景
- 日志记录:在方法执行前后记录日志
- 权限校验:在访问敏感资源前检查权限
- 事务管理:Spring 的
@Transactional就是通过代理实现 - 延迟加载:只有真正需要时才加载对象
- 远程代理:RPC 框架中的服务调用
静态代理
代理类在编译期就已经确定,需要手动编写。
// 1. 定义接口
public interface UserService {
void save(User user);
}
// 2. 目标类
public class UserServiceImpl implements UserService {
@Override
public void save(User user) {
System.out.println("保存用户: " + user.getName());
}
}
// 3. 代理类
public class UserServiceProxy implements UserService {
private final UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public void save(User user) {
System.out.println("=== 开始事务 ===");
try {
target.save(user);
System.out.println("=== 提交事务 ===");
} catch (Exception e) {
System.out.println("=== 回滚事务 ===");
throw e;
}
}
}
// 4. 使用
UserService proxy = new UserServiceProxy(new UserServiceImpl());
proxy.save(new User("张三"));缺点:每个目标类都需要手写一个代理类,维护成本高。
JDK 动态代理
在运行时动态生成代理类,要求目标类必须实现接口。
// 1. 实现 InvocationHandler
public class LogInvocationHandler implements InvocationHandler {
private final Object target;
public LogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强
System.out.println("方法执行前: " + method.getName());
long start = System.currentTimeMillis();
try {
// 调用目标方法
Object result = method.invoke(target, args);
// 后置增强
long cost = System.currentTimeMillis() - start;
System.out.println("方法执行后: " + method.getName() + ", 耗时: " + cost + "ms");
return result;
} catch (Exception e) {
// 异常处理
System.out.println("方法执行异常: " + e.getMessage());
throw e;
}
}
}
// 2. 创建代理对象
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器
target.getClass().getInterfaces(), // 目标类实现的接口
new LogInvocationHandler(target) // 处理器
);
// 3. 调用
proxy.save(new User("张三"));输出:
方法执行前: save
保存用户: 张三
方法执行后: save, 耗时: 5msCGLIB 动态代理
当目标类没有实现接口时,可以使用 CGLIB。它通过继承目标类,在子类中重写方法来实现代理。
// 1. 目标类(不需要实现接口)
public class OrderService {
public void createOrder(String orderId) {
System.out.println("创建订单: " + orderId);
}
}
// 2. 实现 MethodInterceptor
public class LogMethodInterceptor implements MethodInterceptor {
/**
* @param obj 代理对象(目标类的子类)
* @param method 被拦截的方法
* @param args 方法参数
* @param proxy 方法代理,用于调用父类方法
*/
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
// 前置增强
System.out.println("CGLIB 前置: " + method.getName());
// 关键点:必须使用 invokeSuper 调用父类方法
// 如果使用 invoke(obj, args),会导致无限递归,栈溢出!
Object result = proxy.invokeSuper(obj, args);
// 后置增强
System.out.println("CGLIB 后置: " + method.getName());
return result;
}
}
// 3. 创建代理对象
public static void main(String[] args) {
// 创建 Enhancer 对象
Enhancer enhancer = new Enhancer();
// 设置父类(被代理的目标类)
enhancer.setSuperclass(OrderService.class);
// 设置回调(拦截器)
enhancer.setCallback(new LogMethodInterceptor());
// 创建代理对象(目标类的子类)
OrderService proxy = (OrderService) enhancer.create();
// 调用方法
proxy.createOrder("ORDER_001");
}输出:
CGLIB 前置: createOrder
创建订单: ORDER_001
CGLIB 后置: createOrderCGLIB 注意事项:
- 不能代理 final 类和 final 方法:因为无法继承或重写
- 必须使用
invokeSuper:使用invoke会导致无限递归 - 需要引入依赖:Spring 已内置,独立使用需添加 cglib 依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>Spring AOP 如何选择代理方式?
- 如果目标对象实现了接口 → 默认使用 JDK 动态代理
- 如果目标对象没有实现接口 → 使用 CGLIB 代理
- 可以通过配置强制使用 CGLIB:
@EnableAspectJAutoProxy(proxyTargetClass = true)
责任链模式 (Chain of Responsibility)
核心思想
将多个处理器(Handler)连成一条链,请求沿着链传递,每个处理器决定是处理请求还是传递给下一个处理器。
应用场景
- 审批流程:请假审批(组长 → 经理 → CEO)
- 过滤器链:Servlet Filter、Spring Security FilterChain
- 规则校验:订单校验、参数校验
- 日志处理:不同级别的日志由不同处理器处理
完整示例
// 1. 请求类
@Data
@AllArgsConstructor
public class LeaveRequest {
private String name; // 申请人
private int days; // 请假天数
private String reason; // 请假原因
}
// 2. 抽象处理器
public abstract class LeaveHandler {
protected String handlerName;
protected LeaveHandler nextHandler;
public LeaveHandler(String handlerName) {
this.handlerName = handlerName;
}
// 设置下一个处理器,返回下一个处理器(支持链式调用)
public LeaveHandler setNext(LeaveHandler next) {
this.nextHandler = next;
return next;
}
// 处理请求
public abstract void handle(LeaveRequest request);
// 传递给下一个处理器
protected void passToNext(LeaveRequest request) {
if (nextHandler != null) {
nextHandler.handle(request);
} else {
System.out.println("审批结束,无人能处理此请求");
}
}
}
// 3. 具体处理器:组长(可批准1天以内)
public class TeamLeaderHandler extends LeaveHandler {
public TeamLeaderHandler() {
super("组长");
}
@Override
public void handle(LeaveRequest request) {
if (request.getDays() <= 1) {
System.out.println(handlerName + "批准了" + request.getName() +
"的请假申请,天数:" + request.getDays());
} else {
System.out.println(handlerName + "无权批准,转交上级");
passToNext(request);
}
}
}
// 4. 具体处理器:经理(可批准3天以内)
public class ManagerHandler extends LeaveHandler {
public ManagerHandler() {
super("经理");
}
@Override
public void handle(LeaveRequest request) {
if (request.getDays() <= 3) {
System.out.println(handlerName + "批准了" + request.getName() +
"的请假申请,天数:" + request.getDays());
} else {
System.out.println(handlerName + "无权批准,转交上级");
passToNext(request);
}
}
}
// 5. 具体处理器:CEO(可批准7天以内)
public class CeoHandler extends LeaveHandler {
public CeoHandler() {
super("CEO");
}
@Override
public void handle(LeaveRequest request) {
if (request.getDays() <= 7) {
System.out.println(handlerName + "批准了" + request.getName() +
"的请假申请,天数:" + request.getDays());
} else {
System.out.println(handlerName + "拒绝了请假申请,天数太长:" +
request.getDays());
}
}
}
// 6. 使用
public static void main(String[] args) {
// 构建责任链:组长 -> 经理 -> CEO
LeaveHandler leader = new TeamLeaderHandler();
leader.setNext(new ManagerHandler())
.setNext(new CeoHandler());
// 测试不同天数的请假
leader.handle(new LeaveRequest("张三", 1, "身体不适")); // 组长批准
leader.handle(new LeaveRequest("李四", 3, "家中有事")); // 经理批准
leader.handle(new LeaveRequest("王五", 5, "出国旅游")); // CEO批准
leader.handle(new LeaveRequest("赵六", 10, "环游世界")); // CEO拒绝
}输出:
组长批准了张三的请假申请,天数:1
组长无权批准,转交上级
经理批准了李四的请假申请,天数:3
组长无权批准,转交上级
经理无权批准,转交上级
CEO批准了王五的请假申请,天数:5
组长无权批准,转交上级
经理无权批准,转交上级
CEO拒绝了请假申请,天数太长:10模板模式 (Template Method)
核心思想
在父类中定义一个算法的骨架,将某些步骤的具体实现延迟到子类。使得子类可以在不改变算法整体结构的情况下,重新定义某些步骤。
应用场景
- 支付流程:校验 → 扣款 → 通知,其中扣款逻辑不同
- 报表生成:获取数据 → 格式化 → 输出,其中数据源不同
- Spring 中:
JdbcTemplate、RestTemplate、RedisTemplate
完整示例
// 1. 抽象模板类
public abstract class AbstractOrderHandler {
// 模板方法:定义处理订单的完整流程
// 使用 final 防止子类修改算法骨架
public final void handleOrder(Order order) {
// 1. 参数校验(公共步骤)
validate(order);
// 2. 库存检查(公共步骤)
checkStock(order);
// 3. 计算价格(抽象步骤,子类必须实现)
BigDecimal price = calculatePrice(order);
// 4. 创建支付记录(抽象步骤)
createPayment(order, price);
// 5. 发送通知(钩子方法,子类可选择是否重写)
if (needNotify()) {
sendNotification(order);
}
// 6. 记录日志(公共步骤)
logOrder(order);
}
// 公共步骤:所有子类共用
private void validate(Order order) {
System.out.println("校验订单参数...");
if (order == null || order.getItems().isEmpty()) {
throw new IllegalArgumentException("订单不能为空");
}
}
private void checkStock(Order order) {
System.out.println("检查库存...");
}
private void logOrder(Order order) {
System.out.println("记录订单日志: " + order.getId());
}
// 抽象步骤:强制子类实现
protected abstract BigDecimal calculatePrice(Order order);
protected abstract void createPayment(Order order, BigDecimal price);
// 钩子方法:提供默认实现,子类可选择重写
protected boolean needNotify() {
return true; // 默认需要通知
}
protected void sendNotification(Order order) {
System.out.println("发送订单通知: " + order.getId());
}
}
// 2. 具体实现:普通订单
public class NormalOrderHandler extends AbstractOrderHandler {
@Override
protected BigDecimal calculatePrice(Order order) {
System.out.println("普通订单价格计算");
return order.getTotalPrice(); // 原价
}
@Override
protected void createPayment(Order order, BigDecimal price) {
System.out.println("创建普通订单支付: " + price);
}
}
// 3. 具体实现:VIP订单
public class VipOrderHandler extends AbstractOrderHandler {
@Override
protected BigDecimal calculatePrice(Order order) {
System.out.println("VIP订单价格计算:享受8折优惠");
return order.getTotalPrice().multiply(new BigDecimal("0.8"));
}
@Override
protected void createPayment(Order order, BigDecimal price) {
System.out.println("创建VIP订单支付: " + price);
}
// 重写钩子方法:VIP订单不需要普通通知
@Override
protected boolean needNotify() {
return false;
}
// 重写通知方法:VIP专属通知渠道
@Override
protected void sendNotification(Order order) {
System.out.println("发送VIP专属通知: " + order.getId());
}
}
// 4. 使用
public static void main(String[] args) {
Order order = new Order("ORDER_001", Arrays.asList(...));
System.out.println("=== 处理普通订单 ===");
new NormalOrderHandler().handleOrder(order);
System.out.println("\n=== 处理VIP订单 ===");
new VipOrderHandler().handleOrder(order);
}模板模式三要素:
| 类型 | 关键字 | 说明 |
|---|---|---|
| 模板方法 | final | 定义算法骨架,防止子类修改流程 |
| 抽象方法 | abstract | 强制子类实现,每个子类有不同实现 |
| 钩子方法 | 普通方法 | 提供默认实现,子类按需重写 |
总结对比
| 模式 | 核心思想 | 典型应用 |
|---|---|---|
| 单例模式 | 全局唯一实例 | 配置类、连接池、线程池 |
| 工厂模式 | 封装对象创建 | Spring BeanFactory |
| 策略模式 | 封装可互换算法 | 支付方式、折扣规则 |
| 代理模式 | 控制对象访问 | Spring AOP、RPC |
| 责任链模式 | 请求链式传递 | Filter、权限校验 |
| 模板模式 | 定义算法骨架 | JdbcTemplate |
