概述
一般情況下,當(dāng)我們想給一個(gè)類或?qū)ο筇砑庸δ艿臅r(shí)候铺遂,有兩種常用的方式:
- 繼承:通過使用繼承己肮,我們可以使子類既能擁有父類的功能士袄,也能實(shí)現(xiàn)本身的功能。
- 組合:而組合的方式谎僻,是在某一個(gè)類中包裝另一個(gè)類的對(duì)象窖剑,然后通過這個(gè)對(duì)象引用來決定是否擁有該類的某些功能,我們把這個(gè)包裝的對(duì)象可以稱為裝飾器 (Decorator)戈稿;
由于繼承是一種靜態(tài)的行為西土,而組合則可以實(shí)現(xiàn)動(dòng)態(tài)的往一個(gè)類中添加的新的行為。并且就功能而言鞍盗,組合相比繼承更加靈活需了,這樣可以給某個(gè)對(duì)象而不是整個(gè)類添加一些功能跳昼。而裝飾者模式就是基于組合來實(shí)現(xiàn)的:
裝飾者模式是一種動(dòng)態(tài)地往一個(gè)類中添加新的行為的設(shè)計(jì)模式。
裝飾者模式簡介
??也就是說肋乍,通過使用裝飾者模式鹅颊,可以在運(yùn)行時(shí)擴(kuò)充一個(gè)類的功能。大概原理是:增加一個(gè)裝飾類包裝原來的類墓造,包裝的方式一般是通過在將原來的對(duì)象作為裝飾類的構(gòu)造函數(shù)的參數(shù)堪伍。裝飾類實(shí)現(xiàn)新的功能,但是觅闽,在不需要用到新功能的地方帝雇,它可以直接調(diào)用原來的類中的方法。修飾類必須和原來的類有相同的接口蛉拙。
??修飾模式是類繼承的另外一種選擇尸闸。類繼承在編譯時(shí)候增加行為,而裝飾模式是在運(yùn)行時(shí)增加行為孕锄。
??當(dāng)有幾個(gè)相互獨(dú)立的功能需要擴(kuò)充時(shí)吮廉,這個(gè)區(qū)別就變得很重要。在有些面向?qū)ο蟮木幊陶Z言中畸肆,類不能在運(yùn)行時(shí)被創(chuàng)建宦芦,通常在設(shè)計(jì)的時(shí)候也不能預(yù)測(cè)到有哪幾種功能組合。這就意味著要為每一種組合創(chuàng)建一個(gè)新類轴脐。相反踪旷,裝飾者模式是面向運(yùn)行時(shí)候的對(duì)象實(shí)例的,這樣就可以在運(yùn)行時(shí)根據(jù)需要進(jìn)行組合。
結(jié)構(gòu)
我們先通過一張圖來看下裝飾者模式的結(jié)構(gòu):
圖片來源:圖說設(shè)計(jì)模式 - 裝飾模式 - 結(jié)構(gòu)圖解
通過以上結(jié)構(gòu)豁辉,我們可以大概了解到裝飾者模式的幾個(gè)角色:
- Component,最基礎(chǔ)的底層接口舀患,包含基礎(chǔ)的功能方法徽级;
- ConcreteComponent ,底層接口Component原始的實(shí)現(xiàn)聊浅,我們要裝飾的對(duì)象就是這部分餐抢,這部分也就是被裝飾者;
- Decorator低匙,裝飾角色旷痕,一般情況下該對(duì)象是一個(gè)抽象類,實(shí)現(xiàn)自Component顽冶,在該類中一般會(huì)有一個(gè)指向Component接口的對(duì)象欺抗,指向被裝飾的對(duì)象,通過組合的形式對(duì)其進(jìn)行包裝强重;
- ConcreteDecorator绞呈,具體的裝飾角色贸人,繼承自Decorator缩宜,對(duì)具體的被裝飾者進(jìn)行包裝走触,并且可以添加新的功能;
我們先通過簡單的代碼來看一下:
-
Component
接口:
public interface Component {
/**
* 底層基礎(chǔ)接口
*/
void operation();
}
-
ConcreteComponent
實(shí)現(xiàn)類:
public class ConcreteComponent implements Component {
/**
* 基礎(chǔ)接口的實(shí)現(xiàn)
*/
@Override
public void operation() {
System.out.println("concrete component");
}
}
-
Decorator
裝飾角色褐筛,抽象類圾亏,實(shí)現(xiàn)Component:
public abstract class Decorator implements Component {
/** 維護(hù)一個(gè)被裝飾的對(duì)象的引用*/
protected Component component;
/**
* 通過構(gòu)造方法傳入被裝飾的對(duì)象
* @param component
*/
public Decorator(Component component) {
this.component = component;
}
/**
* 調(diào)用被裝飾的對(duì)象的方法
*/
@Override
public void operation() {
component.operation();
}
}
-
ConcreteDecorator
具體裝飾角色十拣,繼承自Decorator抽象類:
public class ConcreteDecorator extends Decorator {
/**
* 自定義自己的屬性
*/
private String addState = "test";
public ConcreteDecorator(Component component) {
super(component);
}
@Override
public void operation() {
component.operation();
System.out.println("addState:" + addState);
}
/**
* 自定義新的方法,實(shí)現(xiàn)新的功能
*/
public void addedBehavior() {
System.out.println("addState:" + addState);
}
// 省略掉get,set方法
}
-
Main
志鹃,用于測(cè)試:
public class Main {
public static void main(String[] args) {
Component component = new ConcreteDecorator(new ConcreteComponent());
component.operation();
}
}
優(yōu)缺點(diǎn)
- 我們先來看下優(yōu)點(diǎn):
a. 裝飾者模式可以用來代替繼承關(guān)系夭问,可以動(dòng)態(tài)的擴(kuò)展一個(gè)實(shí)現(xiàn)類的功能,且不影響到其他對(duì)象弄跌;
b. 裝飾者模式不管最終裝飾了多少層甲喝,最終返回的對(duì)象還是Component;
c. 遵循設(shè)計(jì)模式的原則:對(duì)擴(kuò)展開放铛只,對(duì)修改關(guān)閉埠胖。
- 有優(yōu)點(diǎn)就免不了會(huì)有缺點(diǎn),我們?cè)賮砜聪氯秉c(diǎn)淳玩,缺點(diǎn)的話其實(shí)就比較明顯了:
裝飾者模式采用組合的形式比繼承靈活直撤,但也比繼承相對(duì)復(fù)雜,如果裝飾的層數(shù)過多蜕着,出現(xiàn)問題排查的時(shí)候也會(huì)相對(duì)困難些谋竖;因此,盡量減少裝飾類的數(shù)量承匣,以便降低系統(tǒng)的復(fù)雜度蓖乘;
適用場(chǎng)景
裝飾者模式的適用場(chǎng)景可以根據(jù)它的優(yōu)點(diǎn)來進(jìn)行選擇:
- 當(dāng)需要在不影響其他對(duì)象的情況下,動(dòng)態(tài)的為一個(gè)類擴(kuò)展功能的時(shí)候韧骗;
- 當(dāng)不能使用繼承或者不適合采用繼承的方式進(jìn)行類的擴(kuò)展和維護(hù)時(shí)(比如類定義為final類型)嘉抒,可以采用裝飾者模式;
在JAVA I/O流中的應(yīng)用
??而裝飾者模式在Java中應(yīng)用最廣泛最出名的恐怕就是Java中的I/O的API了袍暴。如果我們?cè)趯W(xué)習(xí)I/O體系前些侍,沒有了解過裝飾者模式的話,那么由于I/O體系本身復(fù)雜的結(jié)構(gòu)政模,學(xué)習(xí)的時(shí)候或許會(huì)有一種很頭疼的感覺岗宣。我們還是先來看下I/O體系大致結(jié)構(gòu):
注:沒有完全展示所有I/O相關(guān)的接口。 圖片來源:google 圖片
我們可以大致把上圖分為幾層:
- Reader淋样、Writer耗式、InputStream、OutputStream,對(duì)應(yīng)裝飾者角色中最基礎(chǔ)的接口:Component纽什,這里面包含了基礎(chǔ)的操作措嵌;
- FileInputStream,F(xiàn)ileOutputStream等第二層沒有其他實(shí)現(xiàn)類的類芦缰,對(duì)應(yīng)于裝飾者中被裝飾的角色:ConcreteComponent企巢;
- FilterReader,F(xiàn)ilterOutputStream等让蕾,對(duì)應(yīng)于裝飾者中的抽象裝飾角色:Decorator浪规;
- BufferedOutputStream,DataOutputStream等探孝,對(duì)應(yīng)于裝飾者中的具體裝飾角色:ConcreteDecorator笋婿;
我們接下來可以通過一個(gè)代碼簡單看下實(shí)現(xiàn):
public class Main {
public static void main(String[] args) throws Exception {
// 1. 定義輸入流
InputStream inputStream = null;
try {
// 2. 實(shí)例 被裝飾者 : 文件讀取
inputStream = new FileInputStream("E://test.txt");
// 3. 具體裝飾角色:緩沖流
inputStream = new BufferedInputStream(inputStream);
// 4. 進(jìn)行操作:開始讀文件
byte[] text = new byte[inputStream.available()];
inputStream.read(text);
System.out.println(new String(text));
} catch (IOException e) {
e.printStackTrace();
} finally {
inputStream.close();
}
}
}
當(dāng)然,裝飾者模式在Java中應(yīng)用的地方還有許多地方顿颅,比如最近學(xué)習(xí)的Mybatis中Executor體系結(jié)構(gòu):Executor
, BaseExecutor
, CachingExecutor
等缸濒。
總結(jié)
學(xué)習(xí)了裝飾者模式之后,我們來簡單回顧總結(jié)下:
- 裝飾者模式可以動(dòng)態(tài)的擴(kuò)展類的功能粱腻,就這點(diǎn)來說庇配,裝飾者模式要比使用繼承更為靈活,按照GoF設(shè)計(jì)模式的劃分绍些,裝飾者模式屬于結(jié)構(gòu)型模式的一種捞慌。
- 裝飾者模式遵循了
對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉
的設(shè)計(jì)原則柬批,在不修改原有代碼的基礎(chǔ)上啸澡,通過組合的形式擴(kuò)展新的功能。- 裝飾者模式雖然比繼承的形式要靈活氮帐,但由于其本身的繁瑣性嗅虏,所以也會(huì)相對(duì)繼承復(fù)雜些,如果裝飾的層數(shù)過多上沐,一旦出現(xiàn)問題皮服,將會(huì)提高我們排查問題的難度,所以要盡量減少裝飾的層數(shù)奄容,以便降低系統(tǒng)的復(fù)雜度。
本文參考自:
裝飾器模式--繼承的另一個(gè)選擇
維基百科-裝飾模式