裝飾模式定義
裝飾模式(Decorator Pattern)- 動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé),就增加功能來(lái)說(shuō),裝飾模式相比生成子類更為靈活
裝飾模式也可以叫做包裝器(Wrapper),可以給一個(gè)類或?qū)ο笤黾有袨?/p>
通常有兩種方式來(lái)給類或?qū)ο髞?lái)增加行為:
- 繼承機(jī)制:通過(guò)繼承一個(gè)現(xiàn)有的類可以使得子類在擁有自己方法的同時(shí)還可以擁有父類的方法,但是這種方式是靜態(tài)的,用戶不能控制增加行為的方式和時(shí)機(jī)
- 關(guān)聯(lián)機(jī)制:將一個(gè)對(duì)象嵌到另一個(gè)對(duì)象中,并由另一個(gè)對(duì)象來(lái)決定是否調(diào)用嵌入對(duì)象的行為來(lái)擴(kuò)展自己的行為,這個(gè)嵌入對(duì)象就是裝飾器(Decorator)
模式結(jié)構(gòu)
角色職責(zé)
- 抽象構(gòu)件(Component):一個(gè)接口或抽象類,定義原始的對(duì)象
- 具體被裝飾對(duì)象(ConcreteComponent):具體的構(gòu)件實(shí)現(xiàn),被裝飾的對(duì)象,可以給這個(gè)對(duì)象添加一些職責(zé)
- 抽象裝飾類(Decorator):持有一個(gè)指向Component實(shí)例的引用,并定義一個(gè)與Component接口一致的接口
- 具體裝飾者(ConcreteDecorator):具體的裝飾對(duì)象,負(fù)責(zé)給構(gòu)件對(duì)象添加職責(zé)
類圖結(jié)構(gòu)

代碼實(shí)現(xiàn)
大家在看視頻的時(shí)候,視頻網(wǎng)站都會(huì)在播放之前給你來(lái)一段小廣告... 然后告訴你充錢可以變強(qiáng),可以不用看廣告,有一些視頻像綜藝節(jié)目或電影在播放完后還會(huì)有彩蛋
抽象構(gòu)件,定義一個(gè)播放視頻的接口
public interface Video {
/**
* 播放
*/
void play();
}
具體的構(gòu)件,被修飾的對(duì)象,寫一個(gè)播放電影的類
/**
* Film 具體構(gòu)件,被裝飾的對(duì)象
* @author zouxq
*/
public class Movie implements Video{
@Override
public void play() {
System.out.println("看電影");
}
}
抽象裝飾類
/**
* 抽象裝飾類
* @author zouxq
*/
public abstract class BaseVideoDecorator implements Video {
private Video video;
// 通過(guò)構(gòu)造函數(shù)傳遞被修飾者
public BaseVideoDecorator(Video video) {
this.video = video;
}
// 委托給修飾者執(zhí)行
@Override
public void play() {
video.play();
}
}
具體的裝飾類,用來(lái)給具體的構(gòu)件添加職責(zé),這里給播放視頻的對(duì)象添加播放廣告的職責(zé)
/**
* 具體的修飾類
* @author zouxq
*/
public class AdvertisingVideoDecorator extends BaseVideoDecorator {
// 定義被修飾者
public AdvertisingVideoDecorator(Video video) {
super(video);
}
@Override
public void play() {
addAdvertising();
super.play();
}
// 定義修飾的方法
private void addAdvertising(){
System.out.println("來(lái)看個(gè)廣告,充錢可以不用看哦");
}
}
下面的裝飾類用來(lái)添加播放彩蛋的職責(zé)
/**
* 具體的修飾類
* @author zouxq
*/
public class EasterEggsVideoDecorator extends BaseVideoDecorator {
// 定義被修飾者
public EasterEggsVideoDecorator(Video video) {
super(video);
}
@Override
public void play() {
super.play();
addEggs();
}
// 定義修飾的方法
private void addEggs(){
System.out.println("加個(gè)彩蛋...");
}
}
測(cè)試使用
@Test
public void decoratorTest() {
Video movie = new Movie();
movie.play();
System.out.println("第一次裝飾......");
movie = new AdvertisingVideoDecorator(movie);
movie.play();
System.out.println("第二次裝飾......");
movie = new EasterEggsVideoDecorator(movie);
movie.play();
}
測(cè)試結(jié)果
看電影
第一次裝飾......
來(lái)看個(gè)廣告,充錢可以不用看哦
看電影
第二次裝飾......
來(lái)看個(gè)廣告,充錢可以不用看哦
看電影
加個(gè)彩蛋...
在上面的例子,我們是把四個(gè)角色都寫出來(lái)了,但是實(shí)際運(yùn)用的時(shí)候根據(jù)實(shí)際情況還可以更加的靈活。比如,如果能確定被裝飾類就一個(gè)對(duì)象,可以只有一個(gè)ConcreteComponent類而不需要抽象的Component類,而Decorator類可以是ConcreteComponent的一個(gè)子類;同樣我們只需為添加一個(gè)職責(zé),就可以不要建立一個(gè)單獨(dú)的Decoratorl類,直接寫一個(gè)ConcreteDecorator就可以了
裝飾模式的優(yōu)缺點(diǎn)和使用場(chǎng)景
優(yōu)缺點(diǎn)
- 裝飾類和被裝飾類可以相互獨(dú)立,不會(huì)相互耦合。裝飾類Decorator是從外部來(lái)擴(kuò)展被裝飾類Component的,不需要知道Component的內(nèi)部實(shí)現(xiàn)
- 裝飾模式是繼承的一種替代方案,修飾返回的對(duì)象還是Component,是is-a的關(guān)系
- 裝飾模式可以動(dòng)態(tài)的擴(kuò)展一個(gè)類的功能
- 可以把原始類中的裝飾功能放到裝飾類中,把類的核心職責(zé)和裝飾功能區(qū)分開
裝飾模式的一大缺點(diǎn)就是:多層的裝飾會(huì)比較復(fù)雜,裝飾的順序也是很重要的,盡量要減少裝飾類的數(shù)量,避免太過(guò)復(fù)雜
使用場(chǎng)景
- 需要擴(kuò)展一個(gè)類的功能
- 動(dòng)態(tài)的給對(duì)象增加功能,并且可以動(dòng)態(tài)的撤銷(繼承是靜態(tài)擴(kuò)展)
為什么選擇裝飾模式而不是繼承
從上面的例子和總結(jié),我們可以看出使用裝飾模式的好處,那么裝飾模式和繼承同樣是可以擴(kuò)展類的功能,為什么會(huì)更偏向使用裝飾模式
我們用一個(gè)例子來(lái)說(shuō)明一下,我們?nèi)ワ嬈返耆ベI一杯飲料,比如買一杯奶茶MilkyTea,這時(shí)服務(wù)員問(wèn)你想要加什么,可以加紅豆、椰奶、珍珠... 至于加料之后的奶茶我們很容易想到使用繼承去實(shí)現(xiàn),比如紅豆OrmosiaMilkyTea、椰奶CoconutMilkyTea、珍珠PearlMilkyTea,假如要加多種料呢?紅豆椰奶OrmosiaCoconutMilkyTea ......
到這里就會(huì)發(fā)現(xiàn),使用繼承會(huì)要寫許多的類,這里才3種加料,假如是可以加十幾二十種配料呢,那自由組合的可能性就太多了,使用繼承就會(huì)使我們的子類爆炸。使用繼承我們要添加的功能是靜態(tài)的,添加的功能便是在子類中實(shí)現(xiàn),在編譯時(shí)就已經(jīng)確定下來(lái);而使用裝飾模式則是動(dòng)態(tài)的添加功能,我需要什么功能便使用對(duì)應(yīng)的裝飾類去裝飾,裝飾的種類和順序都是在客戶端動(dòng)態(tài)的加載,更加的靈活