问题
用过哪些设计模式?策略模式和代理模式有什么区别?
答案
1. 常用设计模式概览
在实际开发中,最常用的设计模式包括:
创建型模式:
- 单例模式:Spring Bean、数据库连接池
- 工厂模式:BeanFactory、DAO工厂
- 建造者模式:StringBuilder、Lombok的@Builder
结构型模式:
- 代理模式:Spring AOP、MyBatis Mapper接口
- 装饰器模式:Java IO流(BufferedReader包装FileReader)
- 适配器模式:SpringMVC的HandlerAdapter
行为型模式:
- 策略模式:支付方式选择、排序算法
- 模板方法模式:JdbcTemplate、抽象类定义流程
- 观察者模式:Spring事件机制、消息队列
2. 策略模式详解
2.1 定义与核心思想
定义:定义一系列算法,将每个算法封装起来,并使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端。
核心要素:
- 策略接口(Strategy):定义算法族的公共接口
- 具体策略(ConcreteStrategy):实现不同的算法
- 上下文(Context):持有策略引用,根据需要调用策略
2.2 实现示例
场景:电商平台支付方式
// 1. 策略接口
public interface PaymentStrategy {
void pay(BigDecimal amount);
}
// 2. 具体策略:支付宝
@Component("alipay")
public class AlipayStrategy implements PaymentStrategy {
@Override
public void pay(BigDecimal amount) {
System.out.println("使用支付宝支付:" + amount);
// 调用支付宝SDK
}
}
// 3. 具体策略:微信支付
@Component("wechat")
public class WechatPayStrategy implements PaymentStrategy {
@Override
public void pay(BigDecimal amount) {
System.out.println("使用微信支付:" + amount);
// 调用微信支付SDK
}
}
// 4. 具体策略:银行卡支付
@Component("bankcard")
public class BankCardStrategy implements PaymentStrategy {
@Override
public void pay(BigDecimal amount) {
System.out.println("使用银行卡支付:" + amount);
// 调用银行接口
}
}
// 5. 上下文:订单服务
@Service
public class OrderService {
@Autowired
private Map<String, PaymentStrategy> strategies; // Spring自动注入所有策略
public void checkout(Order order, String paymentType) {
// 根据支付类型动态选择策略
PaymentStrategy strategy = strategies.get(paymentType);
if (strategy == null) {
throw new IllegalArgumentException("不支持的支付方式");
}
// 执行支付
strategy.pay(order.getAmount());
}
}
使用示例:
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/checkout")
public Result checkout(@RequestBody Order order,
@RequestParam String paymentType) {
// paymentType: alipay / wechat / bankcard
orderService.checkout(order, paymentType);
return Result.success();
}
}
2.3 策略模式的优势
消除条件语句:
改造前(硬编码):
public void pay(String type, BigDecimal amount) {
if ("alipay".equals(type)) {
System.out.println("支付宝支付");
} else if ("wechat".equals(type)) {
System.out.println("微信支付");
} else if ("bankcard".equals(type)) {
System.out.println("银行卡支付");
}
// 新增支付方式需要修改此方法(违反开闭原则)
}
改造后(策略模式):
// 新增支付方式只需添加新策略类,无需修改现有代码
@Component("crypto")
public class CryptoPayStrategy implements PaymentStrategy {
public void pay(BigDecimal amount) {
System.out.println("数字货币支付");
}
}
3. 代理模式详解
3.1 定义与核心思想
定义:为目标对象提供一个代理,通过代理控制对目标对象的访问。
核心要素:
- 抽象主题(Subject):定义真实对象和代理对象的共同接口
- 真实主题(RealSubject):被代理的目标对象
- 代理(Proxy):持有真实对象引用,控制访问并增强功能
3.2 实现示例
场景:用户服务加权限控制
静态代理:
// 1. 抽象主题
public interface UserService {
void deleteUser(Long userId);
}
// 2. 真实主题
public class UserServiceImpl implements UserService {
@Override
public void deleteUser(Long userId) {
System.out.println("删除用户:" + userId);
// 实际删除逻辑
}
}
// 3. 代理类:增加权限控制
public class UserServiceProxy implements UserService {
private UserService target;
private String currentUser;
public UserServiceProxy(UserService target, String currentUser) {
this.target = target;
this.currentUser = currentUser;
}
@Override
public void deleteUser(Long userId) {
// 前置增强:权限检查
if (!"admin".equals(currentUser)) {
throw new SecurityException("无权限删除用户");
}
// 调用真实对象
target.deleteUser(userId);
// 后置增强:记录日志
System.out.println("管理员 " + currentUser + " 删除了用户 " + userId);
}
}
使用示例:
UserService realService = new UserServiceImpl();
UserService proxy = new UserServiceProxy(realService, "admin");
proxy.deleteUser(1001L); // 通过代理访问
动态代理(JDK):
// JDK动态代理:基于接口
public class JdkProxyFactory {
public static <T> T createProxy(T target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强
System.out.println("Before: " + method.getName());
// 执行目标方法
Object result = method.invoke(target, args);
// 后置增强
System.out.println("After: " + method.getName());
return result;
}
}
);
}
}
CGLIB动态代理:
// CGLIB动态代理:基于子类(目标类无接口时使用)
public class CglibProxyFactory {
public static <T> T createProxy(Class<T> clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
throws Throwable {
System.out.println("CGLIB Before: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("CGLIB After: " + method.getName());
return result;
}
});
return (T) enhancer.create();
}
}
3.3 Spring AOP中的代理模式
// Spring通过代理实现AOP
@Aspect
@Component
public class LogAspect {
// 环绕通知:完全控制目标方法执行
@Around("execution(* com.example.service.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 前置增强
System.out.println("方法执行前:" + joinPoint.getSignature());
long startTime = System.currentTimeMillis();
// 调用目标方法(相当于代理调用真实对象)
Object result = joinPoint.proceed();
// 后置增强
long endTime = System.currentTimeMillis();
System.out.println("方法执行后,耗时:" + (endTime - startTime) + "ms");
return result;
}
}
4. 策略模式 vs 代理模式
| 维度 | 策略模式 | 代理模式 |
|---|---|---|
| 目的 | 封装可替换的算法族,让算法独立于客户端变化 | 控制对目标对象的访问,增强或限制其功能 |
| 关注点 | 算法的选择和切换 | 对象的访问控制和功能增强 |
| 结构 | 客户端主动选择策略 | 客户端通过代理间接访问目标对象 |
| 接口 | 策略接口定义算法规范 | 代理和真实对象实现同一接口 |
| 扩展性 | 新增策略无需修改上下文 | 新增功能需修改代理类(静态代理)或切面(动态代理) |
| 典型场景 | 支付方式、排序算法、折扣计算 | 权限控制、日志记录、事务管理、延迟加载 |
| Spring中应用 | Resource加载策略、事务传播行为 | AOP(@Transactional、@Cacheable、安全校验) |
5. 核心区别详解
5.1 意图不同
策略模式:
- 目的:让客户端选择不同的行为(算法)
- 本质:定义一组可替换的算法
- 客户端视角:我要选择哪种方式完成任务
代理模式:
- 目的:控制对目标对象的访问,增加额外职责
- 本质:在不改变目标对象的前提下扩展功能
- 客户端视角:我要访问这个对象,但可能有额外的控制或增强
5.2 类图对比
策略模式类图:
Client --> Context --> Strategy <<interface>>
^
|
+----------------+----------------+
| | |
AlipayStrategy WechatPayStrategy BankCardStrategy
(客户端选择策略,Context持有策略引用)
代理模式类图:
Client --> Proxy --> Subject <<interface>>
| ^
| |
+----> RealSubject
(客户端访问代理,代理持有真实对象引用)
5.3 代码示例对比
策略模式关键代码:
// 客户端主动选择策略
public void checkout(Order order, String paymentType) {
PaymentStrategy strategy = strategies.get(paymentType); // 选择策略
strategy.pay(order.getAmount()); // 执行算法
}
代理模式关键代码:
// 代理在执行前后增强功能
public void deleteUser(Long userId) {
checkPermission(); // 前置增强
target.deleteUser(userId); // 调用真实对象
logOperation(userId); // 后置增强
}
6. 实际项目中的应用
6.1 策略模式实战
场景:电商促销活动
// 策略接口
public interface DiscountStrategy {
BigDecimal calculate(BigDecimal originalPrice);
}
// 满减策略
@Component("fullReduction")
public class FullReductionStrategy implements DiscountStrategy {
public BigDecimal calculate(BigDecimal originalPrice) {
return originalPrice.compareTo(new BigDecimal("100")) >= 0
? originalPrice.subtract(new BigDecimal("20"))
: originalPrice;
}
}
// 折扣策略
@Component("discount")
public class PercentDiscountStrategy implements DiscountStrategy {
public BigDecimal calculate(BigDecimal originalPrice) {
return originalPrice.multiply(new BigDecimal("0.8")); // 8折
}
}
// VIP策略
@Component("vip")
public class VipDiscountStrategy implements DiscountStrategy {
public BigDecimal calculate(BigDecimal originalPrice) {
return originalPrice.multiply(new BigDecimal("0.7")); // 7折
}
}
6.2 代理模式实战
场景:数据库查询缓存
@Aspect
@Component
public class CacheAspect {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Around("@annotation(Cacheable)")
public Object cache(ProceedingJoinPoint joinPoint) throws Throwable {
// 生成缓存key
String key = generateKey(joinPoint);
// 查询缓存
Object cached = redisTemplate.opsForValue().get(key);
if (cached != null) {
return cached;
}
// 缓存未命中,执行目标方法
Object result = joinPoint.proceed();
// 写入缓存
redisTemplate.opsForValue().set(key, result, 30, TimeUnit.MINUTES);
return result;
}
}
7. 面试答题总结
标准回答模板:
常用设计模式:
- 创建型:单例(Spring Bean)、工厂(BeanFactory)、建造者(StringBuilder)
- 结构型:代理(AOP)、装饰器(IO流)、适配器(HandlerAdapter)
- 行为型:策略(支付方式)、模板方法(JdbcTemplate)、观察者(事件机制)
策略模式 vs 代理模式:
- 策略模式:封装可替换的算法族,客户端选择不同策略完成任务(如支付方式选择)
- 代理模式:控制对目标对象的访问,在不改变目标对象的前提下增强功能(如AOP的权限控制、日志记录)
核心区别:
- 意图:策略模式关注算法的选择和替换,代理模式关注功能的增强和控制
- 结构:策略模式客户端主动选择策略,代理模式客户端通过代理间接访问目标对象
- 应用场景:策略模式用于业务规则多变场景,代理模式用于横切关注点(日志、事务、权限)
常见追问:
- 策略模式如何避免if-else? → 通过Map存储策略实例,根据key动态获取
- Spring AOP使用哪种代理? → 默认JDK动态代理(基于接口),无接口时用CGLIB(基于子类)
- 装饰器模式和代理模式的区别? → 装饰器关注增强对象功能(层层包装),代理关注控制访问(单层代理)