當前最新:百度工程師教你玩轉設計模式(裝飾器模式)

2022-12-23 14:10:42 來源:51CTO博客

作者 | 北極星小組


(資料圖)

想要寫好代碼,設計模式(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場景的應用

裝飾器模式,一個典型的應用就是文件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 ----------

推薦閱讀【技術加油站】系列:

??百度工程師教你玩轉設計模式(工廠模式)??

??百度工程師教你玩轉設計模式(適配器模式)??

??百度工程師教你玩轉設計模式(單例模式)??

標簽: 任務處理 設計模式 處理完成

上一篇:全球新動態:[ Linux ] 死鎖以及如何避免死鎖
下一篇:SpringBoot2.x系列教程48--多數據源配置之AOP動態切換數據源