1、定義
裝飾者模式動(dòng)態(tài)地將責(zé)任附加到對(duì)象上。如果要擴(kuò)展功能,裝飾者提供了比繼承更有彈性的替代方案。
2、UML類圖

角色說(shuō)明
- Component:抽象組件,接口或抽象類,被裝飾者。
- ConcreteComponent:具體組件
- Decorator:裝飾者抽象類,繼承被裝飾者的同時(shí)關(guān)聯(lián)被裝飾者(持有引用),同時(shí)添加自己的行為
- ConcreteDecorator:具體裝飾類。
3、模式演進(jìn)
以星巴克為例,有多種類型咖啡,特濃咖啡、混合咖啡、烘焙咖啡等。這里創(chuàng)建一個(gè)咖啡的基類,包含description與cost屬性。每種類型的咖啡可能會(huì)添加多種不同的調(diào)料,比如牛奶、摩卡、泡沫....
//基類
abstract class Beverage {
private String description;
public String getDescription() {
return description;
}
public Beverage(String description) {
this.description = description;
}
abstract public double cost();
}
class Espresso extends Beverage{
public Espresso() {
super("特濃咖啡");
}
@Override
public double cost() {
return 25;
}
}
class HouseBlend extends Beverage{
public HouseBlend() {
super("混合咖啡");
}
@Override
public double cost() {
return 35;
}
}
class DarkRoast extends Beverage{
public DarkRoast() {
super("烘焙咖啡");
}
@Override
public double cost() {
return 28;
}
}
演進(jìn)一
如果為第種類型的不同口味的咖啡都創(chuàng)建一個(gè)子類,可能會(huì)引起類爆炸(加1份奶,1份摩卡,2份奶......),對(duì)于維護(hù)來(lái)說(shuō)是一種惡夢(mèng)。
演進(jìn)二
使用實(shí)例變量和繼承來(lái)追蹤這些調(diào)料,cost不再是抽象方法
abstract class Beverage {
private boolean milk,mocha,whip;//牛奶,摩卡,泡沫
private String description;
public Beverage(String description) {
this.description = description;
}
public boolean isMilk() {
return milk;
}
public void setMilk(boolean milk) {
this.milk = milk;
}
public boolean isMocha() {
return mocha;
}
public void setMocha(boolean mocha) {
this.mocha = mocha;
}
public boolean isWhip() {
return whip;
}
public void setWhip(boolean whip) {
this.whip = whip;
}
public String getDescription() {
if(isMilk()) {
description += " 牛奶";
}
if(isMocha()) {
description += " 摩卡";
}
if(isWhip()) {
description += " 泡沫";
}
return description;
}
public double cost() {
double total = 0;
if(isMilk()) {
total += 2;
}
if(isMocha()) {
total += 4;
}
if(isWhip()) {
total += 3.5;
}
return total;
};
}
class Espresso extends Beverage {
public Espresso() {
super("特濃咖啡");
}
@Override
public double cost() {
return 25 + super.cost();
}
}
class HouseBlend extends Beverage {
public HouseBlend() {
super("混合咖啡");
}
@Override
public double cost() {
return 35 + super.cost();
}
}
class DarkRoast extends Beverage {
public DarkRoast() {
super("烘焙咖啡");
}
@Override
public double cost() {
return 28 + super.cost();
}
}
客戶端測(cè)試
Beverage b = new HouseBlend();
b.setMilk(true);
System.out.println(b.getDescription() + ":" + b.cost());
Beverage b2 = new Espresso();
b2.setMocha(true);
System.out.println(b2.getDescription() + ":"+ b2.cost());
Beverage b3 = new DarkRoast();
b3.setWhip(true);
System.out.println(b3.getDescription() + ":"+ b3.cost());
輸出結(jié)果
混合咖啡 牛奶:37.0
特濃咖啡 摩卡:29.0
烘焙咖啡 泡沫:31.5
- 優(yōu)點(diǎn):
- 類不會(huì)爆炸
- 增加一種類型的咖啡時(shí),不影響以前的代碼,符合開(kāi)閉原則
class Decaf extends Beverage{
public Decaf() {
super("低咖啡因咖啡");
}
@Override
public double cost() {
return 29 + super.cost();
}
}
- 缺點(diǎn):
1、增加一種新的調(diào)料時(shí),需要修改Beverage的cost方法,不符合開(kāi)閉原則
2、如果想要加2份牛奶,貌似比較麻煩
演進(jìn)三 裝飾者模式
以咖啡為主體,在運(yùn)行時(shí)以調(diào)料來(lái)“裝飾”咖啡。比方說(shuō),顧客想要摩卡和牛奶烘焙咖啡。那么要做的是
- 拿一個(gè)烘焙咖啡對(duì)象(DarkRoast);
- 以摩卡對(duì)象(Mocha)裝飾它,變成摩卡烘焙咖啡,加上摩卡的價(jià)格;
- 以牛奶對(duì)象(Milk)裝飾摩卡烘焙咖啡,變成摩卡和牛奶烘焙咖啡,加上牛奶的價(jià)格;
如果要雙份牛奶,就進(jìn)行下一步
- 以牛奶對(duì)象(Milk)裝飾牛奶和摩卡烘焙咖啡,變成摩卡和牛奶和牛奶烘焙咖啡,再加上一份牛奶的價(jià)格;
通過(guò)以上步驟分析:
- 摩卡和牛奶是“裝飾者”,烘焙咖啡就是“被裝飾者”;
- 裝飾完的對(duì)象可以繼續(xù)裝飾,說(shuō)明裝飾者與被裝飾者的類型是一致的;
- 裝飾者可以在被裝飾者的行為基礎(chǔ)上添加自己的行為(例如在烘焙咖啡基礎(chǔ)加上摩卡的價(jià)格),這就需要裝飾者持有被裝飾者的引用
- 可以運(yùn)行時(shí)動(dòng)態(tài)地裝飾對(duì)象
現(xiàn)在,讓我們來(lái)設(shè)計(jì)星巴克咖啡
- 設(shè)計(jì)裝飾者調(diào)料抽象類Condiment
- 繼承被裝飾者Beverage
- 關(guān)聯(lián)Beverage
- 實(shí)現(xiàn)具體裝飾者類(牛奶、摩卡.....),并添加自己的行為
abstract class Condiment extends Beverage{
protected Beverage beverage;
public Condiment(Beverage beverage) {
super("調(diào)料");
this.beverage = beverage;
}
}
class Milk extends Condiment {
public Milk(Beverage beverage) {
super(beverage);
}
@Override
public double cost() {
return beverage.cost() + 2;
}
@Override
public String getDescription() {
return beverage.getDescription() + "牛奶";
}
}
class Mocha extends Condiment {
public Mocha(Beverage beverage) {
super(beverage);
}
@Override
public double cost() {
return beverage.cost() + 4;
}
@Override
public String getDescription() {
return beverage.getDescription() + "摩卡";
}
}
最終UML類圖為

客戶端測(cè)試
public static void main(String[] args) {
Beverage b = new HouseBlend();
Beverage b2 = new Milk(b);//加牛奶
Beverage b3 = new Mocha(b2);//加摩卡
Beverage b4 = new Mocha(b3);//加摩卡
System.out.println(b4.getDescription() + ":" + b4.cost());
}
輸出結(jié)果
混合咖啡牛奶摩卡摩卡:45.0
4、裝飾者模式說(shuō)明
優(yōu)點(diǎn):
- 用組合可以實(shí)現(xiàn)運(yùn)行時(shí)動(dòng)態(tài)擴(kuò)展,比繼承的編譯時(shí)靜態(tài)決定要靈活
- 當(dāng)增加新的裝飾時(shí),可以不改動(dòng)現(xiàn)有的代碼,符合開(kāi)閉原則
缺點(diǎn):
- 引入裝飾者會(huì)增加大量的小類,導(dǎo)致設(shè)計(jì)不容易被理解
其他:
- 裝飾者Codiment與被裝飾者Beverage并不是is-a的關(guān)系,這里用繼承的目的是為了達(dá)到類型匹配,而不是利用繼承獲得行為,行為是通過(guò)組合進(jìn)行動(dòng)態(tài)添加的。
5、Android中的裝飾者模式
5.1 Context
Context在Android開(kāi)發(fā)中表示“上下文”。Context類是一個(gè)抽象類,具體實(shí)現(xiàn)在ContexImpl中。繼承關(guān)系如下

- 其中ContextWarpper繼承Context并持有Context的引用,這里對(duì)應(yīng)模式中的裝飾者,Context為被裝飾者。
public class ContextWrapper extends Context {
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
//........
}
- Application、Service、Activity為具體裝飾類。
5.2 Drawable
Drawable也是Android開(kāi)發(fā)中經(jīng)常用到的一個(gè)概念。是一個(gè)”可繪制東西“的抽象。用來(lái)做繪制相關(guān)的操作。跟View不同樣,Drawable 不能接受任何事件以及用戶交互。

- DrawableWrapper為抽象裝飾者,繼承Drawable的同時(shí)持有Drawable類型的引用。
- ShapeDrawable、BitmapDrawable、LayerDrawable都是我們開(kāi)發(fā)中用過(guò)具體實(shí)現(xiàn)類,只不過(guò)我們一般通過(guò)XML方式來(lái)定義。
- ClipDrawable、InsertDrawable、RotateDrawable、ScaleDrawable為具體裝飾者??蓪?shí)現(xiàn)一些額外的裝飾效果。比如ClipDrwable可實(shí)現(xiàn)自身裁剪復(fù)制顯示功能。