一、概述
裝飾者模式(Decorator Pattern)允許向一個現(xiàn)有的對象擴展新的功能,同時不改變其結(jié)構(gòu)。主要解決直接繼承下因功能的不斷橫向擴展導致子類膨脹的問題,無需考慮子類的維護。
裝飾者模式有4種角色:
抽象構(gòu)件角色(Component):具體構(gòu)件類和抽象裝飾者類的共同父類。
具體構(gòu)件角色(ConcreteComponent):抽象構(gòu)件的子類,裝飾者類可以給它增加額外的職責。
裝飾角色(Decorator):抽象構(gòu)件的子類,具體裝飾類的父類,用于給具體構(gòu)件增加職責,但在子類中實現(xiàn)。
具體裝飾角色(ConcreteDecorator):具體裝飾類,定義了一些新的行為,向構(gòu)件類添加新的特性。

二、入門案例
2.1、類圖

2.2、基礎(chǔ)類介紹
// 抽象構(gòu)件角色
public interface Component {
void doSomeThing();
}
// 具體構(gòu)件角色
public class ConcreteComponent implements Component {
@Override
public void doSomeThing(){
System.out.println("處理業(yè)務(wù)邏輯");
}
}
// 裝飾者類
public abstract class Decorator implements Component {
private Component component;
public Decorator(Component component){
this.component = component;
}
@Override
public void doSomeThing(){
// 調(diào)用處理業(yè)務(wù)邏輯
component.doSomeThing();
}
}
// 具體裝飾類
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component){
super(component);
}
@Override
public void doSomeThing(){
System.out.println("業(yè)務(wù)邏輯功能擴展");
super.doSomeThing();
}
}
當然,如果需要擴展更多功能的話,可以再定義其他的ConcreteDecorator類,實現(xiàn)其他的擴展功能。如果只有一個ConcreteDecorator類,那么就沒有必要建立一個單獨的Decorator類,而可以把Decorator和ConcreteDecorator的責任合并成一個類。
三、應(yīng)用場景
如風之前在一家保險公司干過一段時間。其中保險業(yè)務(wù)員也會在自家產(chǎn)品注冊賬號,進行推銷。不過在這之前,他們需要經(jīng)過培訓,導入一張展業(yè)資格證書。然后再去推銷保險產(chǎn)品供用戶下單,自己則通過推銷產(chǎn)生的業(yè)績,參與分潤,拿對應(yīng)的傭金。
對于上面導證書這個場景,實際上是會根據(jù)不同的保險產(chǎn)品,導入不同的證書的。并且證書的類型也不同,對應(yīng)的解析、校驗、執(zhí)行的業(yè)務(wù)場景都是不同的。如何去實現(xiàn)呢?當然if-else確實也是一種不錯的選擇。下面放一段偽代碼
/**
* @author 往事如風
* @version 1.0
* @date 2022/11/17 11:32
* @description
*/
@RestController
@RequestMapping("/certificate")
public class CertificateController {
@Resource
private CommonCertificateService certificateService;
@PostMapping("/import")
public Result<Integer> importFile(@RequestParam MultipartFile file, @RequestParam String productCode){
return Result.success(certificateService.importCertificate(file, productCode));
}
}
/**
* @author 往事如風
* @version 1.0
* @date 2022/11/17 13:25
* @description
*/
@Service
public class CommonCertificateService {
public Integer importCertificate(MultipartFile file, String productCode){
// 1、參數(shù)非空校驗
// 2、通過file后綴判斷file類型,支持excel和pdf
// 3、解析file文件,獲取數(shù)據(jù),統(tǒng)一封裝到定義的CertificatePojo類中
// 4、根據(jù)產(chǎn)品類型判斷導入之前的業(yè)務(wù)邏輯
if (productCode.equals(DecorateConstants.PRODUCT_A)) {
// 重新計算業(yè)績邏輯
// 重新算業(yè)績類型邏輯
// 一坨坨代碼去實現(xiàn)....
}
else if (productCode.equals(DecorateConstants.PRODUCT_B)) {
// 導入證書的代理人自己以及上級身份晉升邏輯
// 業(yè)績計算邏輯
// 一坨坨代碼去實現(xiàn)...
} else if (productCode.equals(DecorateConstants.PRODUCT_C)) {
// c產(chǎn)品下的業(yè)務(wù)邏輯
// 一坨坨代碼去實現(xiàn)...
} else {
// 默認的處理邏輯
// 一坨坨代碼去實現(xiàn)...
}
// 5、證書數(shù)據(jù)保存
// 6、代理人信息保存
// 7、相關(guān)流水數(shù)據(jù)保存
// 返回代理人id
Integer agentId = Integer.MAX_VALUE;
return agentId;
}
}
從上面的偽代碼看到,所有的業(yè)務(wù)邏輯是在一起處理的,通過productCode去處理對應(yīng)產(chǎn)品的相關(guān)邏輯。這么一看,好像也沒毛病,但是還是被技術(shù)大佬給否決了。好吧,如風決定重寫。運用裝飾者模式,重新處理下了下這段代碼。1、一切再從注解出發(fā),自定義Decorate注解,這里定義2個屬性,scene和type
- scene:標記具體的業(yè)務(wù)場景
- type:表示在該種業(yè)務(wù)場景下,定義一種具體的裝飾器類
/**
* @author 往事如風
* @version 1.0
* @date 2022/11/8 17:44
* @description
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Service
public @interface Decorate {
/**
* 具體的業(yè)務(wù)場景
* @return
*/
String scene();
/**
* 類型:不同業(yè)務(wù)場景下,不同的裝飾器類型
* @return
*/
String type();
}
2、抽象構(gòu)件接口,BaseHandler,這個是必須滴
/**
* @author 往事如風
* @version 1.0
* @date 2022/11/8 17:07
* @description 抽象處理接口
*/
public interface BaseHandler<T, R> {
/**
* 統(tǒng)一的處理方法
* @param t
* @return
*/
R handle(T t);
}
3、抽象裝飾器類,AbstractHandler,持有一個被裝飾類的引用,這個引用具體在運行時被指定
/**
* @author 往事如風
* @version 1.0
* @date 2022-11-13 22:10:05
* @desc 抽象父類
*/
public abstract class AbstractHandler<T, R> implements BaseHandler<T, R> {
protected BaseHandler service;
public void setService(BaseHandler service){
this.service = service;
}
}
4、具體的裝飾器類AProductServiceDecorate?,主要負責處理“導師證書”這個業(yè)務(wù)場景下,A產(chǎn)品相關(guān)的導入邏輯,并且標記了自定義注解Decorate,表示該類是裝飾器類。主要負責對A產(chǎn)品證書導入之前邏輯的增強,我們這里稱之為“裝飾”。
/**
* @author 往事如風
* @version 1.0
* @date 2022-11-13 23:11:16
* @desc
*/
@Decorate(scene = SceneConstants.CERTIFICATE_IMPORT, type = DecorateConstants.PRODUCT_A)
public class AProductServiceDecorate extends AbstractHandler<MultipartFile, Integer> {
/**
* 重寫父類處理數(shù)據(jù)方法
* @param file
* @return
*/
@Override
public Integer handle(MultipartFile file){
// 解析
CertificatePojo data = parseData(file);
// 校驗
check(data);
// 業(yè)績計算
calAchievement(data.getMobile());
return (Integer) service.handle(data);
}
public CertificatePojo parseData(MultipartFile file){
// file,證書解析
System.out.println("A產(chǎn)品的證書解析......");
CertificatePojo certificatePojo = new CertificatePojo();
certificatePojo.setMobile("12323");
certificatePojo.setName("張三");
certificatePojo.setMemberNo("req_343242ds");
certificatePojo.setEffectDate("2022-10-31:20:20:10");
return certificatePojo;
}
/**
* 證書數(shù)據(jù)校驗
* @param data
*/
public void check(CertificatePojo data){
// 數(shù)據(jù)規(guī)范和重復性校驗
// .....
System.out.println("A證書數(shù)據(jù)校驗......");
}
/**
* 計算業(yè)績信息
*/
private void calAchievement(String mobile){
System.out.println("查詢用戶信息, 手機號:" + mobile);
System.out.println("重新計算業(yè)績...");
}
}
當然,還是其他裝飾類,BProductServiceDecorate,CProductServiceDecorate等等,負責裝飾其他產(chǎn)品,這里就不舉例了。
5、當然還有管理裝飾器類的裝飾器類管理器DecorateManager,內(nèi)部維護一個map,負責存放具體的裝飾器類
/**
* @author 往事如風
* @version 1.0
* @date 2022/11/15 17:18
* @description 裝飾管理器
*/
public class DecorateManager {
/**
* 用于存放裝飾器類
*/
private Map<String, AbstractHandler> decorateHandleMap = new HashMap<>();
/**
* 將具體裝飾器類放在map中
*
* @param handlerList
*/
public void setDecorateHandler(List<AbstractHandler> handlerList){
for (AbstractHandler h : handlerList) {
Decorate annotation = AnnotationUtils.findAnnotation(h.getClass(), Decorate.class);
decorateHandleMap.put(createKey(annotation.scene(), annotation.type()), h);
}
}
/**
* 返回具體的裝飾器類
*
* @param type
* @return
*/
public AbstractHandler selectHandler(String scene, String type){
String key = createKey(scene, type);
return decorateHandleMap.get(key);
}
/**
* 拼接map的key
* @param scene
* @param type
* @return
*/
private String createKey(String scene, String type){
return StrUtil.builder().append(scene).append(":").append(type).toString();
}
}
6、用了springboot,當然需要將這個管理器交給spring的bean容器去管理,需要創(chuàng)建一個配置類DecorateAutoConfiguration
/**
* @author 往事如風
* @version 1.0
* @date 2022-11-12 19:22:41
* @desc
*/
@Configuration
public class DecorateAutoConfiguration {
@Bean
public DecorateManager handleDecorate(List<AbstractHandler> handlers){
DecorateManager manager = new DecorateManager();
manager.setDecorateHandler(handlers);
return manager;
}
}
7、被裝飾的service類,CertificateService,只需要關(guān)注自己的核心邏輯就可以
/**
* @author 往事如風
* @version 1.0
* @date 2022/11/8 17:10
* @description 執(zhí)行證書導入的service
*/
@Service
public class CertificateService implements BaseHandler<CertificatePojo, Integer> {
/**
* 處理導入證書的核心邏輯service
* @param certificate
* @return
*/
@Override
public Integer handle(CertificatePojo certificate){
System.out.println("核心業(yè)務(wù),證書數(shù)據(jù):" + JSONUtil.toJsonStr(certificate));
// 1、證書數(shù)據(jù)保存
// 2、代理人信息保存
// 3、相關(guān)流水數(shù)據(jù)保存
// 其他的一些列核心操作
Integer agentId = Integer.MAX_VALUE;
// 返回代理人id
return agentId;
}
}
8、在原來的controller中,注入管理器類DecorateManager去調(diào)用,以及service,也就是被裝飾的類。首先拿到裝飾器,然后再通過setService方法,傳入被裝飾的service。也就是具體裝飾什么類,需要在運行時才確定。
/**
* @author 往事如風
* @version 1.0
* @date 2022-11-13 23:30:37
* @desc
*/
@RestController
public class WebController {
@Resource
private DecorateManager decorateManager;
@Resource
private CertificateService certificateService;
@PostMapping("/import")
public Result importFile(@RequestParam MultipartFile file, @RequestParam String productCode){
AbstractHandler handler = decorateManager.selectHandler(SceneConstants.CERTIFICATE_IMPORT, productCode);
if (Objects.isNull(handler)) {
return Result.fail();
}
handler.setService(certificateService);
return Result.success(handler.handle(file));
}
}
下面模擬下代理人導入證書的流程,當選擇A產(chǎn)品,productCode傳A過來,后端的處理流程。
- 對于A產(chǎn)品下,證書的解析,A產(chǎn)品傳的是excel
- 然后數(shù)據(jù)校驗,這個產(chǎn)品下,特有的數(shù)據(jù)校驗
- 最后是核心的業(yè)績重算,只有A產(chǎn)品才會有這個邏輯

當選擇B產(chǎn)品,productCode傳A過來,后端的處理流程。
- 對于B產(chǎn)品下,證書的解析,A產(chǎn)品傳的是pdf
- 然后數(shù)據(jù)校驗,跟A產(chǎn)品也不同,多了xxx步驟
- 核心是代理人的晉升處理,這部分是B產(chǎn)品獨有的

最后說一句,既然都用springboot了,這塊可以寫一個starter,做一個公用的裝飾器模式。如果哪個服務(wù)需要用到,依賴這個裝飾器的starter,然后標記Decorate注解,定義對應(yīng)的scene和type屬性,就可以直接使用了。
四、源碼中運用
4.1、JDK源碼中的運用
來看下IO流中,InputStream、FilterInputStream、FileInputStream、BufferedInputStream的一段代碼
public abstract class InputStream implements Closeable {
public abstract int read() throws IOException;
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = read();
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
}
//--------------------------
public class FilterInputStream extends InputStream {
protected FilterInputStream(InputStream in){
this.in = in;
}
public int read() throws IOException {
return in.read();
}
}
//--------------------------
public class BufferedInputStream extends FilterInputStream {
public BufferedInputStream(InputStream in, int size){
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
public BufferedInputStream(InputStream in){
this(in, DEFAULT_BUFFER_SIZE);
}
public int read() throws IOException {
return in.read();
}
public int read(byte b[], int off, int len) throws IOException {
return in.read(b, off, len);
}
}
//--------------------------
public class FileInputStream extends InputStream {
public int read(byte b[]) throws IOException {
return readBytes(b, 0, b.length);
}
}
再來看下這幾個類的類圖

這些類的代碼有刪改,可以看到BufferedInputStream中定義了很多屬性,這些數(shù)據(jù)都是為了可緩沖讀取來作準備的,看到其有構(gòu)造方法會傳入一個InputStream的實例。實際編碼如下
//被裝飾的對象,文件輸入流
InputStream in=new FileInputStream("/data/log/app.log");
//裝飾對象,可緩沖
InputStream bufferedIn=new BufferedInputStream(in);
bufferedIn.read();
這里覺得很眼熟吧,其實已經(jīng)運用了裝飾模式了。
4.2、mybatis源碼中的運用
在mybatis中,有個接口Executor?,顧名思義這個接口是個執(zhí)行器,它底下有許多實現(xiàn)類,如CachingExecutor、SimpleExecutor、BaseExecutor等等。類圖如下:

主要看下CachingExecutor類,看著很眼熟,很標準的裝飾器。其中該類中的update是裝飾方法,在調(diào)用真正update方法之前,會執(zhí)行刷新本地緩存的方法,對原來的update做增強和擴展。
public class CachingExecutor implements Executor {
private final Executor delegate;
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
public CachingExecutor(Executor delegate){
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
// 增強內(nèi)容
// 修改方法就要清空本地的緩存
flushCacheIfRequired(ms);
// 調(diào)用原有的方法
return delegate.update(ms, parameterObject);
}
}
再來看下BaseExecutor類,這里有一個update方法,這個是原本的被裝飾的update方法。然后再看這個原本的update方法,它調(diào)用的doUpdate方法是個抽象方法,用protected修飾。咦,這不就是模板方法么,關(guān)于模板方法模式,這里就不展開贅述了。
public abstract class BaseExecutor implements Executor {
protected Executor wrapper;
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
clearLocalCache();
return doUpdate(ms, parameter);
}
protected abstract int doUpdate(MappedStatement ms, Object parameter)
throws SQLException;
}
五、總結(jié)
優(yōu)點
通過組合而非繼承的方式,動態(tài)地擴展一個對象的功能,在運行時可以選擇不同的裝飾器從而實現(xiàn)不同的功能。
有效的避免了使用繼承的方式擴展對象功能而帶來的靈活性差、子類無限制擴張的問題。
具體組件類與具體裝飾類可以獨立變化,用戶可以根據(jù)需要新增具體組件類跟裝飾類,在使用時在對其進行組合,原有代碼無須改變,符合"開閉原則"。
缺點
這種比繼承更加靈活機動的特性,也同時意味著更加多的復雜性。
裝飾模式會導致設(shè)計中出現(xiàn)許多小類 (I/O 類中就是這樣),如果過度使用,會使程序變得很復雜。
裝飾模式是針對抽象組件(Component)類型編程。但是,如果你要針對具體組件編程時,就應(yīng)該重新思考你的應(yīng)用架構(gòu),以及裝飾者是否合適。
六、參考源碼
?編程文檔:
https://gitee.com/cicadasmile/butte-java-note
應(yīng)用倉庫:
https://gitee.com/cicadasmile/butte-flyer-parent