裝飾者模式
每天我們出門(mén)前,一定都會(huì)選擇今天上衣穿什么,褲子穿什么,搭配什么鞋子,大衣穿什么。最后一定是做好選擇,打扮好才會(huì)出門(mén)。這個(gè)過(guò)程其實(shí)就是裝飾者模要做的事情 ---- 對(duì)一個(gè)對(duì)象增加額外的功能。
我們?cè)倏匆粋€(gè)例子。我們都吃過(guò)煎餅,除了面餅之外,我們還要加雞蛋、加蔥花、香菜、面醬、辣醬。現(xiàn)在還有新花樣,加辣條、加雞柳。一切都始于一張面餅,攤煎餅的過(guò)程就是在不斷對(duì)這張面餅添加新特性。
我們通過(guò)繼承也可以為對(duì)象增加功能,比如我們有個(gè)煎餅的父類(lèi),默認(rèn)已經(jīng)有面餅、面醬、雞蛋啊。那么我們可以派生出 全都放的普通煎餅、不辣的普通煎餅、不辣不放香菜的普通煎餅、不辣不放蔥的普通煎餅、全都放的辣條煎餅、全都放的雞柳煎餅…… 這只是很小一部分。通過(guò)繼承的話,由于情況太多,會(huì)造成對(duì)象爆炸。
那我們還可以通過(guò)組合的方式來(lái)擴(kuò)展類(lèi)啊,比如煎餅對(duì)象中,我們可以設(shè)置不同屬性,比如是否有蔥、是否有香菜、是否有辣條、是否有雞柳等等。這樣看起來(lái)也能很好的解決攤煎餅的問(wèn)題。但如果想要加腸、加油條怎么辦?想要加兩個(gè)雞蛋怎么辦?我們只能修改煎餅對(duì)象。這就違反了開(kāi)閉原則。顯然這樣也是不夠靈活的。
裝飾者模式能夠很好的解決對(duì)象的動(dòng)態(tài)擴(kuò)展,不管你想穿什么,都可以隨便搭配。不過(guò)這個(gè)煎餅要怎么做,也都能隨意的擴(kuò)展支持,而不需要改已有的代碼。接下來(lái)我們就來(lái)看看如何通過(guò)裝飾者模式來(lái)攤煎餅的。
1. 實(shí)現(xiàn)裝飾者模式
對(duì)于攤煎餅來(lái)說(shuō),我們都是對(duì)于一個(gè)基礎(chǔ)的煎餅對(duì)象做裝飾,比如我想要一套兩個(gè)雞蛋、有辣椒、蔥、辣條的煎餅,那么我只需要先聲明一個(gè)基本的煎餅對(duì)象,然后用加雞蛋裝飾類(lèi)裝飾它,然后再用加辣醬裝飾類(lèi)裝飾它,再用加蔥的裝飾類(lèi)裝飾它,最后再用加辣條的裝飾類(lèi)裝飾它。最終就得到了我想要的煎餅。不過(guò)請(qǐng)注意,不管你怎么裝飾,最終得到的還是煎餅,并不是其他東西。
裝飾者模式的核心思想是對(duì)已有的對(duì)象,一層一層的用裝飾類(lèi)去裝飾它,擴(kuò)展它的特性。這樣做可以更為動(dòng)態(tài)的為對(duì)象增加功能。我們看看代碼如何實(shí)現(xiàn):
先定義煎餅接口:
public interface Pancake {
void cook();
}
接口里只定義了一個(gè)制作方法。
煎餅接口的實(shí)現(xiàn)類(lèi):
public class BasicPancake implements Pancake {
@Override
public void cook() {
System.out.println("加一勺面");
System.out.println("加一個(gè)雞蛋");
}
}
作為一個(gè)最基本的煎餅,總得有面,有雞蛋吧。其他的材料留給裝飾類(lèi)來(lái)實(shí)現(xiàn)。
接下來(lái)我們定義裝飾抽象類(lèi):
public abstract class PancakeDecorator implements Pancake {
protected Pancake pancake;
public void setPancake(Pancake pancake) {
this.pancake = pancake;
}
public void cook() {
if (pancake != null) {
pancake.cook();
}
}
}
可以看到 PancakeDecorator
同樣要實(shí)現(xiàn) Pancke
接口。并且持有 Pancke
類(lèi)型的引用,自己實(shí)現(xiàn)的 cook 方法實(shí)際調(diào)用了持有的 Pancake
對(duì)象的 cook 方法。
加辣醬的裝飾類(lèi)代碼如下,其他裝飾實(shí)現(xiàn)類(lèi)是類(lèi)似的。
public class AddSpicyDecorator extends PancakeDecorator{
@Override
public void cook(){
super.cook();
System.out.println("加辣醬");
}
}
cook 方法首先調(diào)父類(lèi)的 cook 方法,然后再加入自己的特性。
客戶端代碼如下,我們看看如何利用裝飾類(lèi)來(lái)生成你想要的煎餅。
public class Client {
public static void main(String[] args) {
Pancake pancake = new BasicPancake();
PancakeDecorator addEggPancake = new AddEggDecorator();
addEggPancake.setPancake(pancake);
PancakeDecorator addSaucePancake = new AddSauceDecorator();
addSaucePancake.setPancake(addEggPancake);
PancakeDecorator addLaTiaoPancake = new AddLaTiaoDecorator();
addLaTiaoPancake.setPancake(addSaucePancake);
addLaTiaoPancake.cook();
}
}
我們聲明了三個(gè)包裝類(lèi),對(duì) BasicPancake
層層包裝,最后得到一套兩個(gè)雞蛋、加辣醬、加辣條的煎餅。運(yùn)行后輸出如下:
加一勺面
加一個(gè)雞蛋
加一個(gè)雞蛋
加面醬
加辣條
如果你研發(fā)了新煎餅,要加新的輔料,比如香腸、榨菜之類(lèi),那么只需要增加裝飾類(lèi)的實(shí)現(xiàn)即可。從而實(shí)現(xiàn)了開(kāi)閉原則。
類(lèi)圖如下:
2. 裝飾者模式優(yōu)缺點(diǎn)
2.1 優(yōu)點(diǎn)
- 動(dòng)態(tài)的為對(duì)象添加額外職責(zé):通過(guò)組合不同裝飾類(lèi),非常靈活的為對(duì)象增加額外的職責(zé);
- 避免子類(lèi)爆炸:當(dāng)不同的特性組合,構(gòu)成不同的子類(lèi)時(shí),必然造成子類(lèi)爆炸。但通過(guò)裝飾者靈活組合,可以避免這個(gè)問(wèn);
- 分離核心功能和裝飾功能:核心業(yè)務(wù)保留在
Component
的子類(lèi)中。而裝飾特性在Decorator
的實(shí)現(xiàn)類(lèi)中去實(shí)現(xiàn)。面對(duì)裝飾特性的變化,實(shí)現(xiàn)了開(kāi)閉原則,只需要增加裝飾實(shí)現(xiàn)類(lèi); - 很方便的重復(fù)添加特性:我想要一套兩個(gè)雞蛋,雙份辣條的煎餅。是不是只需要多裝飾一次就可以了?就是這么簡(jiǎn)單。
2.2 缺點(diǎn)
- 由于不是通過(guò)繼承實(shí)現(xiàn)添加職責(zé),所以被裝飾后的對(duì)象并不能通過(guò)對(duì)象本身就能了解其特性。而需要分析所有對(duì)其裝飾過(guò)的對(duì)象;
- 裝飾模式會(huì)造成有很多功能類(lèi)似的小對(duì)象。通過(guò)組合不同的裝飾實(shí)現(xiàn),來(lái)達(dá)成不同的需求。這樣對(duì)于不了解系統(tǒng)的人,比較難以學(xué)習(xí)。過(guò)多的裝飾類(lèi)進(jìn)行裝飾,也稍顯繁瑣。
3. 裝飾者模式適用場(chǎng)景
使用裝飾者模式,有以下幾種情況:
- 需要一個(gè)裝飾的載體。不能將全部特性都放在裝飾類(lèi)中。換句話講得有個(gè)裝飾主體,核心特性在主體對(duì)象中實(shí)現(xiàn)。例如瀏覽器窗口,不管是加邊框還是滾動(dòng)條,都是基于窗口的;
- 有多種特性可以任意搭配,對(duì)主體進(jìn)行擴(kuò)展。并且你想以動(dòng)態(tài)、透明的方式來(lái)實(shí);
- 不能以生成子類(lèi)的方式擴(kuò)展。可能有兩種情況,一是對(duì)大量子類(lèi)帶來(lái)的類(lèi)爆炸有所顧慮。二是類(lèi)定義被隱藏,或者不能用于生成子類(lèi)。
4. 小結(jié)
裝飾者模式的優(yōu)勢(shì)在于動(dòng)態(tài)、透明的添加特性。要記住裝飾者裝飾完的對(duì)象還是之前的對(duì)象類(lèi)型。通過(guò)分離核心特性和裝飾特性,客戶端代碼可以靈活的搭配使用包裝對(duì)象,從而得到具有想要行為的對(duì)象。不過(guò)要注意,有些時(shí)候裝飾的順序是要保證的。比如先放雞蛋,再放芝麻,芝麻就不會(huì)掉下去了。最好的做法是保證裝飾類(lèi)的獨(dú)立。