1、定義
裝飾者模式動態(tài)地將責(zé)任附加到對象上陨溅。如果要擴展功能终惑,裝飾者提供了比繼承更有彈性的替代方案。
2门扇、UML類圖
裝飾者模式.png
角色說明
- Component:抽象組件雹有,接口或抽象類,被裝飾者臼寄。
- ConcreteComponent:具體組件
- Decorator:裝飾者抽象類霸奕,繼承被裝飾者的同時關(guān)聯(lián)被裝飾者(持有引用),同時添加自己的行為
- ConcreteDecorator:具體裝飾類吉拳。
3质帅、模式演進
以星巴克為例,有多種類型咖啡留攒,特濃咖啡煤惩、混合咖啡、烘焙咖啡等炼邀。這里創(chuàng)建一個咖啡的基類魄揉,包含description與cost屬性。每種類型的咖啡可能會添加多種不同的調(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;
}
}
演進一
如果為第種類型的不同口味的咖啡都創(chuàng)建一個子類,可能會引起類爆炸(加1份奶兵怯,1份摩卡彩匕,2份奶......),對于維護來說是一種惡夢摇零。
演進二
使用實例變量和繼承來追蹤這些調(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();
}
}
客戶端測試
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)點:
- 類不會爆炸
- 增加一種類型的咖啡時驻仅,不影響以前的代碼谅畅,符合開閉原則
class Decaf extends Beverage{
public Decaf() {
super("低咖啡因咖啡");
}
@Override
public double cost() {
return 29 + super.cost();
}
}
- 缺點:
1、增加一種新的調(diào)料時噪服,需要修改Beverage的cost方法毡泻,不符合開閉原則
2、如果想要加2份牛奶粘优,貌似比較麻煩
演進三 裝飾者模式
以咖啡為主體仇味,在運行時以調(diào)料來“裝飾”咖啡。比方說雹顺,顧客想要摩卡和牛奶烘焙咖啡丹墨。那么要做的是
- 拿一個烘焙咖啡對象(DarkRoast);
- 以摩卡對象(Mocha)裝飾它嬉愧,變成摩卡烘焙咖啡贩挣,加上摩卡的價格;
- 以牛奶對象(Milk)裝飾摩卡烘焙咖啡没酣,變成摩卡和牛奶烘焙咖啡王财,加上牛奶的價格;
如果要雙份牛奶裕便,就進行下一步
- 以牛奶對象(Milk)裝飾牛奶和摩卡烘焙咖啡绒净,變成摩卡和牛奶和牛奶烘焙咖啡,再加上一份牛奶的價格偿衰;
通過以上步驟分析:
- 摩卡和牛奶是“裝飾者”挂疆,烘焙咖啡就是“被裝飾者”;
- 裝飾完的對象可以繼續(xù)裝飾下翎,說明裝飾者與被裝飾者的類型是一致的囱嫩;
- 裝飾者可以在被裝飾者的行為基礎(chǔ)上添加自己的行為(例如在烘焙咖啡基礎(chǔ)加上摩卡的價格),這就需要裝飾者持有被裝飾者的引用
- 可以運行時動態(tài)地裝飾對象
現(xiàn)在漏设,讓我們來設(shè)計星巴克咖啡
- 設(shè)計裝飾者調(diào)料抽象類Condiment
- 繼承被裝飾者Beverage
- 關(guān)聯(lián)Beverage
- 實現(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類圖為
星巴克咖啡.png
客戶端測試
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今妄、裝飾者模式說明
優(yōu)點:
- 用組合可以實現(xiàn)運行時動態(tài)擴展郑口,比繼承的編譯時靜態(tài)決定要靈活
- 當(dāng)增加新的裝飾時鸳碧,可以不改動現(xiàn)有的代碼,符合開閉原則
缺點:
- 引入裝飾者會增加大量的小類犬性,導(dǎo)致設(shè)計不容易被理解
其他:
- 裝飾者Codiment與被裝飾者Beverage并不是is-a的關(guān)系瞻离,這里用繼承的目的是為了達到類型匹配,而不是利用繼承獲得行為乒裆,行為是通過組合進行動態(tài)添加的套利。
5、Android中的裝飾者模式
5.1 Context
Context在Android開發(fā)中表示“上下文”鹤耍。Context類是一個抽象類肉迫,具體實現(xiàn)在ContexImpl中。繼承關(guān)系如下
Context繼承結(jié)構(gòu).png
- 其中ContextWarpper繼承Context并持有Context的引用稿黄,這里對應(yīng)模式中的裝飾者喊衫,Context為被裝飾者。
public class ContextWrapper extends Context {
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
//........
}
- Application杆怕、Service族购、Activity為具體裝飾類。
5.2 Drawable
Drawable也是Android開發(fā)中經(jīng)常用到的一個概念陵珍。是一個”可繪制東西“的抽象寝杖。用來做繪制相關(guān)的操作。跟View不同樣互纯,Drawable 不能接受任何事件以及用戶交互瑟幕。
Drawable繼承關(guān)系.png
- DrawableWrapper為抽象裝飾者,繼承Drawable的同時持有Drawable類型的引用伟姐。
- ShapeDrawable收苏、BitmapDrawable、LayerDrawable都是我們開發(fā)中用過具體實現(xiàn)類愤兵,只不過我們一般通過XML方式來定義鹿霸。
- ClipDrawable、InsertDrawable秆乳、RotateDrawable懦鼠、ScaleDrawable為具體裝飾者∫傺撸可實現(xiàn)一些額外的裝飾效果肛冶。比如ClipDrwable可實現(xiàn)自身裁剪復(fù)制顯示功能。