作者 | 北極星小組
想要寫好代碼,設計模式(Design Pattern)是必不可少的基本功,設計模式是對面向對象設計(Object Oriented Design)中反復出現的一類問題的一種解決方案,本篇介紹裝飾器模式(Decorator Pattern)。
在我們日常的開發過程中,一個最常見的場景就是在已有的基礎上新增功能,常規的做法有以下幾種:
修改已有的類:違背開閉原則。
增加新的子類:每次都得新增大量對應的類,隨著功能的增加,子類越來越膨脹。
在此場景下,裝飾器模式就可以體現出它的優勢了,它允許在不修改原有對象的前提下,靈活的擴展已有類的功能。下面是裝飾器模式的一個通用的類圖:
△UML
其中的各個類的作用如下:
抽象組件(Component): 可以是接口或者抽象類,它定義了具體類以及裝飾器所擁有的方法。
具體組件(ComponentA, ComponentB):具體的組件,實現或者繼承自抽象組件??梢岳斫獬缮鲜鰣鼍爸幸汛嬖诘念?。
抽象裝飾器(Decorator): 通常為抽象類,持有一個被裝飾的對象,定義了具體裝飾器的方法。此類非必須也可以沒有,具體裝飾器也可直接繼承或者實現抽象組件。
具體裝飾器(DecoratorX, DecoratorY): 具體的裝飾器,繼承自抽象裝飾器(也可直接繼承自抽象組件),擴展了抽象組件的某些功能。
下面,將通過3個具體的案例的講解裝飾器的使用方式,方便大家進一步的理解。
在實際的開發中,我們經常需要定義不同的類來處理各種不同的任務。假設一個這樣的場景,我們的系統有多個具體的類,用來處理不同類型的任務?,F在需要添加一個功能,就是在處理完任務后發出一條消息。針對這個場景,使用裝飾器模式的實現思路如下:
抽象組件(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 ----------
推薦閱讀【技術加油站】系列:
|