【Android設計模式】從Context看懂裝飾器模式

會寫代碼是門檻崇棠,寫好代碼是本事

?

前言

平時寫代碼的時候可能為了完成某一個任務而只是應付性地編碼咽袜,然后寫完理直氣壯地來一句"又不是不能用!"枕稀,但如果要把編碼當作一項藝術來打造询刹,那就需要結合我們的設計模式了。設計模式可以運用在諸多方面萎坷,讓我們的代碼更加優(yōu)雅凹联。設計模式在Android中也是無處不在,動態(tài)代理哆档、建造者蔽挠、工廠、適配器....等等等等瓜浸,可見它對我們的重要性澳淑。最近在看Retrofit的源碼,剛好看到了動態(tài)代理插佛,想著總結下這個模式杠巡。

?

什么是裝飾器模式?

裝飾器模式(Decorator Pattern)允許向一個現(xiàn)有的對象添加新的功能雇寇,同時又不改變其結構氢拥。這種類型的設計模式屬于結構型模式蚌铜,相當于對現(xiàn)有的類的一個包裝。

你可能會反駁嫩海,繼承不就可以對現(xiàn)有對象進行拓展了嗎冬殃?繼承是可以實現(xiàn)同樣效果,但是由于Java是單繼承叁怪,如果你多級包裝造壮,子類會越來越多,層級越來越深骂束,而利用裝飾器模式則可以優(yōu)雅解決這個問題耳璧。它動態(tài)地給一個對象添加一些額外的職責。就增加功能來說展箱,裝飾器模式相比繼承更為靈活旨枯。
?

如何實現(xiàn)裝飾器模式?

以繪制形狀為例混驰,我們平時繪制的形狀五花八門攀隔,可能有圓形、矩形栖榨、三角形...等等昆汹,我們抽取出一個抽象的形狀接口:

/**
 * 形狀接口
 */
public interface Shape {
    //定義公用的形狀繪制方法
    void onDraw();
}

接下來,假如我們要繪制一個圓形婴栽,會實現(xiàn)Shape接口满粗,定義具體繪制圓形的方法:

/**
 * 圓形類
 */
public class Circle implements Shape{
    @Override
    public void onDraw() {
        Log.d("Decorator", "繪制一個圓形");
    }
}

假如某一天,有一個特殊的需求愚争,需要繪制一個帶邊框的圓形映皆,但又不允許更改Circle類,如果是按繼承寫法會是如下:

/**
 * 帶邊界的圓形
 */
public class BorderCircle extends Circle{
    @Override
    public void onDraw() {
        super.onDraw();
        Log.d("Decorator","繪制一條邊界");
    }
}

效果是實現(xiàn)了轰枝,也沒出現(xiàn)啥問題捅彻,等到某一天,又來了個需求鞍陨,要繪制一個帶邊界的漸變圓步淹,且依然不能修改原來的類,你也許會想诚撵,那就繼續(xù)繼承BorderCircle就好了......如此反復循環(huán)缭裆,繼承的具體形狀類會越來越多谊迄,且可能會嵌套了很多層,不利于代碼的維護考传。
我們將以上改為裝飾器模式來實現(xiàn)的話殿托,可以先定義一個抽象裝飾者來裝飾Shape:

/**
 * 抽象裝飾者類,對Shape接口進行包裝
 */
public class ShapeDecorator implements Shape{

    private Shape shape;

    public ShapeDecorator(Shape shape) {
        this.shape = shape;
    }

    @Override
    public void onDraw() {
        shape.onDraw();
    }
}

然后假如是實現(xiàn)一個帶邊界的圓形其弊,就是如下寫法:

/**
 * 帶邊界的圓形
 */
public class BorderCircle extends ShapeDecorator{

    public BorderCircle(Shape shape) {
        //調用父類的構造方法霍骄,將參數(shù)傳給父類
        super(shape);
    }

    @Override
    public void onDraw() {
        //調用父類的onDraw
        super.onDraw();
        Log.d("Decorator","繪制一條邊界");
    }
}

假如想要加個漸變群嗤,就不需要再進一步繼承于BorderCircle了畅铭,而是依舊繼承于ShapeDecorator

/**
 * 帶邊界的漸變圓形
 */
public class GradientBorderCircle extends ShapeDecorator{

    public GradientBorderCircle(Shape shape) {
        super(shape);
    }

    @Override
    public void onDraw() {
        super.onDraw();
        Log.d("Decorator", "繪制漸變");
    }
}

最終客戶端調用姿勢如下:

Shape circle = new Circle();
//繪制一個帶邊界的圓形
ShapeDecorator borderCircle = new BorderCircle(circle);
borderCircle.onDraw();
//繪制一個帶邊界且漸變的圓形
ShapeDecorator gradientBorder = new GradientBorderCircle(borderCircle);
gradientBorder.onDraw();

?

Context中的裝飾器模式

Android源碼中也有很多地方用到了這種設計思想氏淑,最經典的Context就使用了這種模式,Context對于我們來說都不陌生硕噩,它代表了當前的上下文對象假残,它本身是一個抽象類:

Context相當于一個【被裝飾者抽象類】

它有一個子類ContextWrapper,可以看到它持有Context對象炉擅,并且在構造方法中賦值辉懒,是不是類似于上面例子中的抽象裝飾者類
ContextWrapper持有Context的引用,它是一個【抽象裝飾者】的角色

而ContextWrapper的子類就有我們最熟悉的Activity谍失、Service眶俩、Application等,它們都相當于Context的具體裝飾者快鱼,各自有各自的特點颠印。
那么問題來了,相比剛才的例子好像還缺一個角色沒有提到——具體被裝飾者類抹竹,剛才已經分析到Application线罕、 Activity 這些都是具體裝飾者,那么它持有的這個Context對象是哪里傳進來的呢窃判?我們以Application為例钞楼,探究下Application中調用attachBaseConetxt的地方:

 /* package */ 
final void attach(Context context) {
        attachBaseContext(context);
        mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}

Applicationattach方法中調用了attachBaseContext,我們繼續(xù)往上跟蹤袄琳,看下哪里調用了attach

static public Application newApplication(Class<?> clazz, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        Application app = (Application)clazz.newInstance();
        app.attach(context);
        return app;
}

Instrumentation類創(chuàng)建了Application窿凤,并且將context綁定給它,那么這個newApplication又是哪里調用的呢跨蟹?

public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        if (mApplication != null) {
            return mApplication;
        }
        Application app = null;
        try {
            //....忽略部分代碼
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            appContext.setOuterContext(app);
        } catch (Exception e) {
            //....忽略部分代碼
        }
        mActivityThread.mAllApplications.add(app);
        mApplication = app;
        //....忽略部分代碼
        return app;
}

到這一步雳殊,可以看到原來傳遞進去的 Context 對象是一個 ContextImpl 類型,那么 ContextImpl 極有可能就是我們的具體被裝飾者窗轩,我們驗證一下夯秃,點擊去可以看到 ContextImpl 確實是繼承于 Context 的:

public final class ContextImpl extends Context {

它們的關系圖如下:

Context Uml

ContextWrapper中實現(xiàn)Context的方法全是通過它持有的mBase對象來實現(xiàn)的。這樣它(ContextWrapper)派生出的子類就可以在不改變原始context(mBase對象)的情況下擴展Context的行為痢艺。 無論是 Activity仓洼、Service還是Application,它們本質都是一個 ContextWrapper堤舒,都是Context的一個具體裝飾者的角色色建,都是通過持有 ContextImpl 對象,在 ContextImpl 的基礎上進一步拓展各自的特色功能和邏輯舌缤。
?

總結

裝飾模式降低了系統(tǒng)的耦合度箕戳,可以動態(tài)增加或刪除對象的職責某残,并使得需要裝飾的具體構件類和具體裝飾類可以獨立變化,以便增加新的具體構件類和具體裝飾類陵吸。裝飾模式應用較為廣泛玻墅,不止Android,例如在 JavaIO 中的輸入流和輸出流的設計也同樣運用了該模式壮虫。當不能采用繼承的方式對系統(tǒng)進行擴展或者采用繼承不利于系統(tǒng)擴展和維護時可以使用裝飾器模式澳厢。
?

GitHubGitHubZJY
CSDN博客IT_ZJYANG
簡 書Android小Y

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市囚似,隨后出現(xiàn)的幾起案子剩拢,更是在濱河造成了極大的恐慌,老刑警劉巖饶唤,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件裸扶,死亡現(xiàn)場離奇詭異,居然都是意外死亡搬素,警方通過查閱死者的電腦和手機呵晨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來熬尺,“玉大人摸屠,你說我怎么就攤上這事×缓撸” “怎么了季二?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長揭措。 經常有香客問我胯舷,道長,這世上最難降的妖魔是什么绊含? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任桑嘶,我火速辦了婚禮,結果婚禮上躬充,老公的妹妹穿的比我還像新娘逃顶。我一直安慰自己,他們只是感情好充甚,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布以政。 她就那樣靜靜地躺著,像睡著了一般伴找。 火紅的嫁衣襯著肌膚如雪盈蛮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天技矮,我揣著相機與錄音抖誉,去河邊找鬼殊轴。 笑死,一個胖子當著我的面吹牛寸五,可吹牛的內容都是我干的。 我是一名探鬼主播耿币,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼梳杏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了淹接?” 一聲冷哼從身側響起十性,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎塑悼,沒想到半個月后劲适,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡厢蒜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年霞势,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片斑鸦。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡愕贡,死狀恐怖,靈堂內的尸體忽然破棺而出巷屿,到底是詐尸還是另有隱情固以,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布嘱巾,位于F島的核電站憨琳,受9級特大地震影響,放射性物質發(fā)生泄漏旬昭。R本人自食惡果不足惜篙螟,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望问拘。 院中可真熱鬧闲擦,春花似錦、人聲如沸场梆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽或油。三九已至寞忿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間顶岸,已是汗流浹背腔彰。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工叫编, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人霹抛。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓搓逾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親杯拐。 傳聞我的和親對象是個殘疾皇子霞篡,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內容