Android設(shè)計(jì)模式-裝飾者模式

1、定義

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

2、UML類圖

裝飾者模式.png

角色說(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):
  1. 類不會(huì)爆炸
  2. 增加一種類型的咖啡時(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ō),顧客想要摩卡和牛奶烘焙咖啡。那么要做的是

  1. 拿一個(gè)烘焙咖啡對(duì)象(DarkRoast);
  2. 以摩卡對(duì)象(Mocha)裝飾它,變成摩卡烘焙咖啡,加上摩卡的價(jià)格;
  3. 以牛奶對(duì)象(Milk)裝飾摩卡烘焙咖啡,變成摩卡和牛奶烘焙咖啡,加上牛奶的價(jià)格;

如果要雙份牛奶,就進(jìn)行下一步

  1. 以牛奶對(duì)象(Milk)裝飾牛奶和摩卡烘焙咖啡,變成摩卡和牛奶和牛奶烘焙咖啡,再加上一份牛奶的價(jià)格;

通過(guò)以上步驟分析:

  • 摩卡和牛奶是“裝飾者”,烘焙咖啡就是“被裝飾者”;
  • 裝飾完的對(duì)象可以繼續(xù)裝飾,說(shuō)明裝飾者與被裝飾者的類型是一致的;
  • 裝飾者可以在被裝飾者的行為基礎(chǔ)上添加自己的行為(例如在烘焙咖啡基礎(chǔ)加上摩卡的價(jià)格),這就需要裝飾者持有被裝飾者的引用
  • 可以運(yùn)行時(shí)動(dòng)態(tài)地裝飾對(duì)象

現(xiàn)在,讓我們來(lái)設(shè)計(jì)星巴克咖啡

  1. 設(shè)計(jì)裝飾者調(diào)料抽象類Condiment
  2. 繼承被裝飾者Beverage
  3. 關(guān)聯(lián)Beverage
  4. 實(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類圖為


星巴克咖啡.png

客戶端測(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)系如下


Context繼承結(jié)構(gòu).png
  • 其中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 不能接受任何事件以及用戶交互。


Drawable繼承關(guān)系.png
  • 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ù)制顯示功能。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容