
作者 | 北極星小組
(資料圖)
想要寫好代碼,設計模式(Design Pattern)是必不可少的基本功,設計模式是對面向對象設計(Object Oriented Design)中反復出現的一類問題的一種解決方案,本篇介紹裝飾器模式(Decorator Pattern)。
在我們日常的開發過程中,一個最常見的場景就是在已有的基礎上新增功能,常規的做法有以下幾種:
修改已有的類:違背開閉原則。增加新的子類:每次都得新增大量對應的類,隨著功能的增加,子類越來越膨脹。在此場景下,裝飾器模式就可以體現出它的優勢了,它允許在不修改原有對象的前提下,靈活的擴展已有類的功能。下面是裝飾器模式的一個通用的類圖:
△UML
其中的各個類的作用如下:
抽象組件(Component): 可以是接口或者抽象類,它定義了具體類以及裝飾器所擁有的方法。具體組件(ComponentA, ComponentB):具體的組件,實現或者繼承自抽象組件。可以理解成上述場景中已存在的類。抽象裝飾器(Decorator): 通常為抽象類,持有一個被裝飾的對象,定義了具體裝飾器的方法。此類非必須也可以沒有,具體裝飾器也可直接繼承或者實現抽象組件。具體裝飾器(DecoratorX, DecoratorY): 具體的裝飾器,繼承自抽象裝飾器(也可直接繼承自抽象組件),擴展了抽象組件的某些功能。下面,將通過3個具體的案例的講解裝飾器的使用方式,方便大家進一步的理解。
在實際的開發中,我們經常需要定義不同的類來處理各種不同的任務。假設一個這樣的場景,我們的系統有多個具體的類,用來處理不同類型的任務。現在需要添加一個功能,就是在處理完任務后發出一條消息。針對這個場景,使用裝飾器模式的實現思路如下:
抽象組件(TaskProcessor):處理任務的抽象類(亦可通過接口實現),定義一個通用的任務處理方法process()。具體組件(TaskProcessorA, TaskProcessorB): 負責實現具體的任務處理邏輯抽象裝飾器(TaskProcessDecorator):持有一個任務處理對象實例具體裝飾器(AfterTaskProcessDecorator):實現具體的任務處理完成后的消息通知擴展能力具體的代碼如下:
package com.baidu.demo;public class Decorator { // 抽象組件 static abstract class TaskProcessor { abstract void process(); } // 具體組件 static class TaskProcessorA extends TaskProcessor { @Override void process() { System.out.println("TaskProcessorA處理完成"); } } // 具體組件 static class TaskProcessorB extends TaskProcessor { @Override void process() { System.out.println("TaskProcessorB處理完成"); } } // 抽象裝飾器 static abstract class TaskProcessDecorator extends TaskProcessor { protected TaskProcessor processor; public TaskProcessDecorator(TaskProcessor processor) { this.processor = processor; } abstract void process(); } // 具體裝飾器 static class AfterTaskProcessDecorator extends TaskProcessDecorator { public AfterTaskProcessDecorator(TaskProcessor processor) { super(processor); } @Override void process() { processor.process(); afterProcess(); } void afterProcess() { System.out.println("任務處理完畢,發送消息..."); } } public static void main(String[] args) { // 擴展之前 System.out.println("==========before=========="); TaskProcessor processorA = new TaskProcessorA(); processorA.process(); TaskProcessor processorB = new TaskProcessorB(); processorB.process(); // 裝飾器擴展之后:TaskProcessorA TaskProcessorB并未做任何修改,即可實現功能的擴展 System.out.println("==========after=========="); TaskProcessor decoratorA = new AfterTaskProcessDecorator(processorA); decoratorA.process(); TaskProcessor decoratorB = new AfterTaskProcessDecorator(processorB); decoratorB.process(); }}// 輸出結果如下==========before==========TaskProcessorA處理完成TaskProcessorB處理完成==========after==========TaskProcessorA處理完成任務處理完畢,發送消息...TaskProcessorB處理完成任務處理完畢,發送消息...
裝飾器模式,一個典型的應用就是文件IO操作,最基礎的類實現字節流讀取類,使用裝飾器模式可以封裝文件字節流讀取類,然后可以繼續封裝可緩存的文件字節流讀取類,在項目中按需使用。具體實現如下:
InputStream:具體組件,實現讀取字節流。FileInputStream:具體裝飾器,作為InputStream的子類,擴展文件操作。BufferedInputStream:具體裝飾器,作為FileInputStream的子類,擴展緩存操作。具體代碼如下:
//具體組件,實現讀取字節流public abstract class InputStream { public int read(byte b[], int off, int len) {}}//具體裝飾器,作為InputStream的子類,擴展文件操作public class FileInputStream extends InputStream { protected InputStream in; public FileInputStream(String name) { InputStream in = ... //此處省略,通過文件名創建對象 this.in = in; } public int read(byte b[], int off, int len) { return this.in.read(b, off, len); }}//具體裝飾器,作為FileInputStream的子類,擴展緩存操作public class BufferedInputStream extends FileInputStream { protected FileInputStream in; protected byte[] buffer; public BufferedInputStream(FileInputStream in) { this.in = in; } public int read(byte b[], int off, int len) { if (this.buffer == null || this.buffer.length == 0) { this.in.read(this.buffer, 0, in.lenght()); } System.arraycopy(this.buffer, off, b, 0, len); ... }}public static void main(String[] args) { FileInputStream fs = new FileInputStream("./test.log"); BufferedInputStream bs = new BufferedInputStream(fs); byte[] b; bs.read(b, 0, 1);}
在日志系統中,一般常用日志的級別分別為 DEBUG(調試)、INFO(運行信息)、WARN(警告)、ERROR(錯誤),一旦發生錯誤級別的日志后,則需要觸發報警通知相關人員及時進行跟進,報警方式一般有:郵件、短信、如流等,通常我們會根據業務場景以組合的方式進行報警通知,使用裝飾器模式則能很好實現組合報警這一功能。
抽象組件:Log接口抽象具體組件:Slf4j 具體日志類的實現抽象裝飾器:LogDecorator 日志裝飾器的基類具體裝飾器:MailLogDecorator、SMSLogDecorator、InfoFlowLogDecorator具體裝飾類/** * 日志接口 */public interface Log { void debug(String message); void info(String message); void warn(String message); void error(String message);}/** * Slf4j 日志 */public class Slf4jLog implements Log { //日志記錄對象 private final Logger log = LoggerFactory.getLogger("system_log"); @Override public void debug(String message) { if (log.isDebugEnabled()) { log.debug(message); } } @Override public void info(String message) { if (log.isInfoEnabled()) { log.info(message); } } @Override public void warn(String message) { if (log.isWarnEnabled()) { log.warn(message); } } @Override public void error(String message) { if (log.isErrorEnabled()) { log.error(message); } }}/** * 日志裝飾器 */public class LogDecorator implements Log { protected Log log; public LogDecorator(Log log) { this.log = log; } @Override public void debug(String message) { log.debug(message); } @Override public void info(String message) { log.info(message); } @Override public void warn(String message) { log.warn(message); } @Override public void error(String message) { log.error(message); }}/** * 郵件日志裝飾器 */public class MailLogDecorator extends LogDecorator { public MailLogDecorator(Log log) { super(log); } @Override public void warn(String message) { log.warn(message); mail(message); } @Override public void error(String message) { log.error(message); mail(message); } public void mail(String message) { //模擬郵件發送 log.info("郵件已發送,信息:" + message); }}/** * 短信日志裝飾器 */public class SMSLogDecorator extends LogDecorator { public SMSLogDecorator(Log log) { super(log); } @Override public void error(String message) { log.error(message); send(message); } public void send(String message) { //模擬短信發送 log.info("短信已發送,信息:" + message); }}/** * 如流日志裝飾器 */public class InfoflowLogDecorator extends LogDecorator { public InfoflowLogDecorator(Log log) { super(log); } @Override public void warn(String message) { log.warn(message); send(message); } @Override public void error(String message) { log.error(message); send(message); } public void send(String message) { //模擬如流發送 log.info("如流消息已發送,信息:" + message); }}/** * 日志測試類 */public class LogTest { /** * 測試日志裝飾器 */ @Test public void testLogDecorator() { Log log = new SMSLogDecorator(new InfoFlowLogDecorator(new MailLogDecorator(new Slf4jLog()))); log.debug("系統調試開啟"); log.info("系統正常運行"); log.warn("數據為空警告"); log.error("db 連接錯誤"); }}===========output=========15:16:56.564 [main] DEBUG system_log - 系統調試開啟15:16:56.566 [main] INFO system_log - 系統正常運行15:16:56.566 [main] WARN system_log - 數據為空警告15:16:56.566 [main] INFO system_log - 郵件已發送,信息:數據為空警告15:16:56.566 [main] INFO system_log - 如流消息已發送,信息:數據為空警告15:16:56.566 [main] ERROR system_log - db 連接錯誤15:16:56.566 [main] INFO system_log - 郵件已發送,信息:db 連接錯誤15:16:56.566 [main] INFO system_log - 如流消息已發送,信息:db 連接錯誤15:16:56.566 [main] INFO system_log - 短信已發送,信息:db 連接錯誤Process finished with exit code 0
如上幾個案例,裝飾器的最大作用就是在不修改原有類的基礎上擴展已有的功能,它符合開閉原則,而且實現也比較靈活。
---------- END ----------
推薦閱讀【技術加油站】系列:
??百度工程師教你玩轉設計模式(工廠模式)??
??百度工程師教你玩轉設計模式(適配器模式)??
??百度工程師教你玩轉設計模式(單例模式)??