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

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)點:
  1. 類不會爆炸
  2. 增加一種類型的咖啡時,不影響以前的代碼,符合開閉原則
class Decaf extends Beverage{
    public Decaf() {
        super("低咖啡因咖啡");
    }
    @Override
    public double cost() {
        return 29 + super.cost();
    }
}
  • 缺點:
    1、增加一種新的調(diào)料時,需要修改Beverage的cost方法,不符合開閉原則
    2、如果想要加2份牛奶,貌似比較麻煩
演進三 裝飾者模式

以咖啡為主體,在運行時以調(diào)料來“裝飾”咖啡。比方說,顧客想要摩卡和牛奶烘焙咖啡。那么要做的是

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

如果要雙份牛奶,就進行下一步

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

通過以上步驟分析:

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

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

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

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