首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

裝飾模式深度解析:從繼承局限到動態組合的靈活擴展藝術

     編輯:Java 綜合教學

前言:為什麼需要裝飾模式?

面對「在不修改原類代碼、不使用繼承」的前提下動態擴展對象功能,裝飾模式提供了優雅的答案。它是開閉原則的經典實踐:對擴展開放,對修改關閉。

裝飾 vs 繼承:核心對比

維度繼承裝飾模式
關係IS-A (靜態、編譯期確定)HAS-A (動態、運行期組合)
擴展方式創建子類重寫方法包裝原對象、委託調用、增強功能
靈活性低:類爆炸、單一繼承限制、無法運行期切換高:任意組合、疊加、運行期決定
耦合度強耦合 (子類依賴父類實現細節)弱耦合 (依賴抽象接口)
功能疊加困難 (需多層繼承或混入)天然支持 (裝飾鏈)

本質:裝飾模式 = 接口/抽象類 + 組合 + 委託 + 功能增強。看似繁瑣,實則賦予了「像搭積木一樣組裝功能」的超能力。

經典案例一:Java IO 流體系 — 裝飾模式教科書級應用

// 核心接口
InputStream (抽象組件)
    │
    ├─ FileInputStream (具體組件) - 數據源
    ├─ ByteArrayInputStream
    └─ FilterInputStream (抽象裝飾者) - 持有 InputStream 引用
         │
         ├─ BufferedInputStream (具體裝飾) - 增加緩衝
         ├─ DataInputStream       - 增加基本類型讀取
         ├─ GZIPInputStream       - 增加解壓
         ├─ CipherInputStream     - 增加解密
         └─ LineNumberInputStream - 增加行號

// 使用:層層包裝,功能疊加
InputStream in = new CipherInputStream(           // 解密
                new GZIPInputStream(              // 解壓
                new BufferedInputStream(          // 緩衝
                new FileInputStream("data.gz")))); // 源

// 每層只關心自己的增強,透明委託給下層

關鍵設計點

  • 統一接口:裝飾者與被裝飾者實現同一接口/繼承同一抽象類
  • 構造器注入:裝飾者構造器接收被裝飾者對象
  • 委託調用:默認方法直接委託 `super.method()`,增強方法在委託前/後/環繞執行邏輯
  • 透明性:客戶端無需知道是否被裝飾,面向接口編程

經典案例二:文本處理管道 — 行號、分號、引號靈活組合

需求:讀取文件行,支持任意組合「添加行號、添加分號、添加引號」

傳統繼承方案 (類爆炸)

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); // 自動記錄日誌、耗時

裝飾模式 vs 代理模式 vs 適配器模式

模式意圖關鍵區別
裝飾動態添加職責接口不變、功能疊加、透明增強
代理控制訪問生命週期管理、遠程/虛擬/保護代理、不一定增強功能
適配器接口轉換接口改變、使不兼容接口協同工作

最佳實踐與避坑指南

  1. 保持輕量:裝飾者應只做單一職責增強,避免「上帝裝飾類」
  2. 接口設計:被裝飾接口應盡量精簡、穩定,避免頻繁變更導致裝飾鏈斷裂
  3. 順序語義:文檔化裝飾順序對行為的影響 (如「加密後壓縮」vs「壓縮後加密」)
  4. 避免過度設計:簡單擴展用繼承/組合即可,只有「運行期動態組合多種可選功能」時才上裝飾模式
  5. Spring AOP 是企業級裝飾:@Transactional、@Cacheable、@Async 本質是編譯期/運行期織入的裝飾器

總結

裝飾模式以組合替代繼承,將單一職責拆解為獨立裝飾類,通過委託鏈實現功能的動態疊加與任意組合。從 Java IO 流到 Spring AOP,從文本處理管道到微服務攔截器,它無處不在。掌握其「接口統一、構造注入、委託增強」三部曲,就掌握了應對「需求變更、功能組合、熱插拔」的通用鑰匙。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved