當需要在不修改原有類的前提下動態增強功能時,裝飾模式是首選。它通過組合而非繼承,將被裝飾對象傳入構造器,並在保留原有行為基礎上添加新功能。IO 流體系中 BufferedReader、LineNumberReader 皆是經典裝飾類。
若用繼承為每種 Reader 添加緩衝功能,需創建 MyBufferReader(MyTextReader)、MyBufferReader(MyMediaReader) 等多個構造器,類爆炸嚴重。裝飾模式只需定義:
class MyBufferReader extends Reader {
private Reader r; // 持有統一父類引用
public MyBufferReader(Reader r) { this.r = r; }
// 增強方法...
}
實現「組合優於繼承」,極大降低耦合,符合開閉原則。
class MyBufferedReader extends Reader {
private Reader r;
public MyBufferedReader(Reader r) { this.r = r; }
// 核心:一次讀一行,處理 \r \n 跨平台換行
public String myReadLine() throws IOException {
StringBuilder sb = new StringBuilder();
int ch;
while ((ch = r.read()) != -1) {
if (ch == '\r') continue; // 忽略 \r
if (ch == '\n') return sb.toString(); // 遇到 \n 返回
sb.append((char) ch);
}
return sb.length() > 0 ? sb.toString() : null;
}
// 必須實現的抽象方法,委託給被裝飾對象
public int read(char[] cbuf, int off, int len) throws IOException {
return r.read(cbuf, off, len);
}
public void close() throws IOException { r.close(); }
}
JDK 提供 LineNumberReader 繼承 BufferedReader,自帶行號統計:
LineNumberReader lnr = new LineNumberReader(new FileReader("buf.txt"));
lnr.setLineNumber(2); // 設置起始行號
String line;
while ((line = lnr.readLine()) != null) {
System.out.println(lnr.getLineNumber() + ":" + line);
}
輸出行號從 3 開始(設值 2 後下一行為 3)。
public class MyLineNumberReader extends MyBufferedReader {
private int lineNumber;
public MyLineNumberReader(Reader r) { super(r); }
public void setLineNumber(int n) { this.lineNumber = n; }
public int getLineNumber() { return lineNumber; }
@Override
public String myReadLine() throws IOException {
lineNumber++;
return super.myReadLine();
}
}
測試時設置 setLineNumber(200),輸出行號從 201 遞增,完美復刻 JDK 行為。
Reader/Writer 及其子類均採用此模式。