面對「在不修改原類代碼、不使用繼承」的前提下動態擴展對象功能,裝飾模式提供了優雅的答案。它是開閉原則的經典實踐:對擴展開放,對修改關閉。
| 維度 | 繼承 | 裝飾模式 |
|---|---|---|
| 關係 | IS-A (靜態、編譯期確定) | HAS-A (動態、運行期組合) |
| 擴展方式 | 創建子類重寫方法 | 包裝原對象、委託調用、增強功能 |
| 靈活性 | 低:類爆炸、單一繼承限制、無法運行期切換 | 高:任意組合、疊加、運行期決定 |
| 耦合度 | 強耦合 (子類依賴父類實現細節) | 弱耦合 (依賴抽象接口) |
| 功能疊加 | 困難 (需多層繼承或混入) | 天然支持 (裝飾鏈) |
本質:裝飾模式 = 接口/抽象類 + 組合 + 委託 + 功能增強。看似繁瑣,實則賦予了「像搭積木一樣組裝功能」的超能力。
// 核心接口
InputStream (抽象組件)
│
├─ FileInputStream (具體組件) - 數據源
├─ ByteArrayInputStream
└─ FilterInputStream (抽象裝飾者) - 持有 InputStream 引用
│
├─ BufferedInputStream (具體裝飾) - 增加緩衝
├─ DataInputStream - 增加基本類型讀取
├─ GZIPInputStream - 增加解壓
├─ CipherInputStream - 增加解密
└─ LineNumberInputStream - 增加行號
// 使用:層層包裝,功能疊加
InputStream in = new CipherInputStream( // 解密
new GZIPInputStream( // 解壓
new BufferedInputStream( // 緩衝
new FileInputStream("data.gz")))); // 源
// 每層只關心自己的增強,透明委託給下層
需求:讀取文件行,支持任意組合「添加行號、添加分號、添加引號」
BufferedReader
├─ LineNumberReader
├─ SemiColonReader
├─ QuoteReader
├─ LineNumberSemiColonReader
├─ LineNumberQuoteReader
├─ SemiColonQuoteReader
└─ LineNumberSemiColonQuoteReader // 7 個子類,還缺排序組合!
// 1. 統一接口
interface LineReader {
String readLine() throws IOException;
void close() throws IOException;
}
// 2. 基礎實現
class FileLineReader implements LineReader {
private BufferedReader reader;
public FileLineReader(String path) throws FileNotFoundException {
this.reader = new BufferedReader(new FileReader(path));
}
public String readLine() throws IOException { return reader.readLine(); }
public void close() throws IOException { reader.close(); }
}
// 3. 抽象裝飾者
abstract class LineReaderDecorator implements LineReader {
protected LineReader delegate;
public LineReaderDecorator(LineReader delegate) { this.delegate = delegate; }
public String readLine() throws IOException { return delegate.readLine(); }
public void close() throws IOException { delegate.close(); }
}
// 4. 具體裝飾者:行號
class LineNumberDecorator extends LineReaderDecorator {
private int line = 1;
public LineNumberDecorator(LineReader delegate) { super(delegate); }
@Override public String readLine() throws IOException {
String line = super.readLine();
return line != null ? (lineNumber++) + " " + line : null;
}
}
// 5. 具體裝飾者:分號
class SemiColonDecorator extends LineReaderDecorator {
public SemiColonDecorator(LineReader delegate) { super(delegate); }
@Override public String readLine() throws IOException {
String line = super.readLine();
return line != null ? line + ";" : null;
}
}
// 6. 具體裝飾者:引號
class QuoteDecorator extends LineReaderDecorator {
public QuoteDecorator(LineReader delegate) { super(delegate); }
@Override public String readLine() throws IOException {
String line = super.readLine();
return line != null ? "\"" + line + "\"" : null;
}
}
// 7. 客戶端:任意組合,順序決定效果
LineReader reader = new QuoteDecorator( // 外層:加引號
new SemiColonDecorator( // 中層:加分號
new LineNumberDecorator( // 內層:加行號
new FileLineReader("test.txt"))));
// 輸出:"1 第一行;"
// 順序改變效果不同:SemiColon(Quote(LineNumber)) → "1 第一行";"
避免為每個接口手寫抽象裝飾類,利用 JDK 動態代理 / CGLIB 實現通用攔截增強:
public class LoggingDecorator implements InvocationHandler {
private Object target;
public LoggingDecorator(Object target) { this.target = target; }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.nanoTime();
log.info("調用: {}.{}", target.getClass().getSimpleName(), method.getName());
try {
return method.invoke(target, args);
} finally {
log.info("耗時: {} ms", (System.nanoTime() - start) / 1e6);
}
}
public static T decorate(T target, Class interfaceClass) {
return interfaceClass.cast(Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class[]{interfaceClass},
new LoggingDecorator(target)
));
}
}
// 使用
UserService userService = new UserServiceImpl();
UserService decorated = LoggingDecorator.decorate(userService, UserService.class);
decorated.getUser(1); // 自動記錄日誌、耗時
| 模式 | 意圖 | 關鍵區別 |
|---|---|---|
| 裝飾 | 動態添加職責 | 接口不變、功能疊加、透明增強 |
| 代理 | 控制訪問 | 生命週期管理、遠程/虛擬/保護代理、不一定增強功能 |
| 適配器 | 接口轉換 | 接口改變、使不兼容接口協同工作 |
裝飾模式以組合替代繼承,將單一職責拆解為獨立裝飾類,通過委託鏈實現功能的動態疊加與任意組合。從 Java IO 流到 Spring AOP,從文本處理管道到微服務攔截器,它無處不在。掌握其「接口統一、構造注入、委託增強」三部曲,就掌握了應對「需求變更、功能組合、熱插拔」的通用鑰匙。