代碼長得像、結構長得像,面試官一問「區別在哪」,許多候選人支支吾吾。本文從對象持有方式、控制權歸屬、核心意圖三個維度,徹底劃清界限,並對應 Spring AOP、IO 流、權限控制等經典實戰場景。
| 維度 | 代理模式 | 裝飾器模式 |
|---|---|---|
| 被代理/裝飾對象來源 | 代理類內部 new 或注入,客戶端不知情 | 客戶端主動傳入,必須知曉被裝飾者 |
| 對象生命週期控制 | 代理控制創建/銷毀/訪問權限 | 客戶端控制,裝飾器只負責「加衣服」 |
| 核心意圖 | 控制訪問:延遲加載、權限攔截、遠程調用、緩存 | 增強職責:動態添加行為、組合功能、不修改原類 |
| 方法調用鏈 | 可阻斷調用(如權限不足直接返回) | 必須轉發給被裝飾者(裝飾前/後增強) |
| 典型介面設計 | 代理與被代理實現同一介面 | 裝飾器實現同一介面,持有介面引用 |
public class UserServiceProxy implements UserService {
private UserService target = new UserServiceImpl(); // 內部持有,客戶端不知情
public void save(User user) {
if (!SecurityUtils.hasPermission("user:save")) {
throw new AccessDeniedException("無權限"); // 可阻斷
}
target.save(user); // 選擇性調用
}
}
public class LoggingInputStream extends InputStream {
private InputStream decorated; // 必須傳入
public LoggingInputStream(InputStream decorated) {
this.decorated = decorated;
}
public int read() {
int b = decorated.read(); // 必須轉發
log.info("讀取字節: {}", b); // 前/後增強
return b;
}
}
BufferedInputStream 裝飾 FileInputStream 添加緩衝;GZIPInputStream 添加解壓(動態組合功能)。TransactionInterceptor: 雖名為 Interceptor,實質是裝飾器,在方法前開啟事務、後提交/回滾(增強行為)。JScrollPane 裝飾 JTextArea 添加滾動條。「兩者結構相似,但意圖本質不同:
代理關注訪問控制,代理類決定是否、何時、如何調用真實對象,客戶端往往不知道代理存在(如 Spring Bean 代理)。
裝飾器關注職責疊加,客戶端主動組合裝飾器鏈,每個裝飾器必須轉發調用並在前後增強,不改變被裝飾者接口契約(如 IO 流)。
判斷技巧:若客戶端必須感知並決定裝飾順序 → 裝飾器;若框架透明植入、客戶端無感 → 代理。」
if-else 權限判斷不需要代理類。@Transactional 失效常見原因:內部方法調用繞過代理,本質是代理模式「自我調用不走代理」的限制。下次面試被問到,直接甩出「控制權在誰手裡、能不能阻斷調用、客戶端知不知情」三連問,再配上 Spring AOP vs IO 流兩大經典案例,穩拿設計模式考察分。