Android常見設(shè)計(jì)模式十二:裝飾者模式

對于開發(fā)人員來說叁熔,設(shè)計(jì)模式有時候就是一道坎,但是設(shè)計(jì)模式又非常有用沸移,過了這道坎黔州,它可以讓你水平提高一個檔次耍鬓。而在android開發(fā)中,必要的了解一些設(shè)計(jì)模式又是必須的流妻,因?yàn)樵O(shè)計(jì)模式在Android源碼中牲蜀,可以說是無處不在。對于想系統(tǒng)的學(xué)習(xí)設(shè)計(jì)模式的同學(xué)绅这,這里推薦一本書涣达,《大話設(shè)計(jì)模式》。


Android常用設(shè)計(jì)模式系列:

面向?qū)ο蟮幕A(chǔ)特征
面向?qū)ο蟮脑O(shè)計(jì)原則
單例模式
模板模式
適配器模式
工廠模式
代理模式
原型模式
策略模式
Build模式
觀察者模式
裝飾者模式
中介模式
門面模式


代理模式

裝飾者模式是非常常見的設(shè)計(jì)模式之一证薇,寫個筆記度苔,記錄一下我的學(xué)習(xí)過程和心得。

首先了解一些裝飾模者式的定義浑度。

動態(tài)地給一個對象添加一些額外的職責(zé)寇窑。就增加功能來說,裝飾模式相比生成子類更為靈活箩张。

  • 裝飾者模式屬于結(jié)構(gòu)型模式甩骏。
  • 裝飾者模式在生活中應(yīng)用實(shí)際上也非常廣泛,一如一間房先慷,放上廚具饮笛,它就是廚房;放上床,就是臥室论熙。
  • 通常我們擴(kuò)展類的功能是通過繼承的方式來實(shí)現(xiàn)福青,但是裝飾者模式是通過組合的方式來實(shí)現(xiàn),這是繼承的替代方案之一脓诡。

涉及角色及說明:

Component(抽象組件):接口或者抽象類无午,被裝飾的最原始的對象。具體組件與抽象裝飾角色的父類祝谚。
ConcreteComponent(具體組件):實(shí)現(xiàn)抽象組件的接口指厌。
Decorator(抽象裝飾角色):一般是抽象類,抽象組件的子類踊跟,同時持有一個被裝飾者的引用踩验,用來調(diào)用被裝飾者的方法;同時可以給被裝飾者增加新的職責(zé)。
ConcreteDecorator(具體裝飾類):抽象裝飾角色的具體實(shí)現(xiàn)商玫。

就以裝修房間為例子箕憾,講一下實(shí)現(xiàn)。

1 創(chuàng)建抽象組件
這里是一個抽象房子類拳昌,定義一個裝修的方法:
    public abstract class Room {
        public abstract void fitment();//裝修方法
    }
2 創(chuàng)建具體組件

現(xiàn)在有一間新房子袭异,已經(jīng)裝上了電:

    public class NewRoom extends Room {//繼承Room
        @Override
        public void fitment() {
            System.out.println("這是一間新房:裝上電");
        }
    }
3 創(chuàng)建抽象裝飾角色

要為房子裝修,定義抽象的房間裝飾類:

     public abstract class RoomDecorator extends Room {//繼承Room炬藤,擁有父類相同的方法
        private Room mRoom;//持有被裝飾者的引用御铃,這里是需要裝修的房間

        public RoomDecorator(Room room) {
            this.mRoom = room;
        }

        @Override
        public void fitment() {
            mRoom.fitment();//調(diào)用被裝飾者的方法
        }
    }
4 創(chuàng)建具體裝飾類

我們要將房間裝修成臥室和廚房碴里,其具體實(shí)現(xiàn)是不同的:

    public class Bedroom extends RoomDecorator {//臥室類,繼承自RoomDecorator

        public Bedroom(Room room) {
            super(room);
        }

        @Override
        public void fitment() {
            super.fitment();
            addBedding();
        }

        private void addBedding() {
            System.out.println("裝修成臥室:添加臥具");
        }
    }

    public class Kitchen extends RoomDecorator {//廚房類上真,繼承自RoomDecorator

        public Kitchen(Room room) {
            super(room);
        }

        @Override
        public void fitment() {
            super.fitment();
            addKitchenware();
        }

        private void addKitchenware() {
            System.out.println("裝修成廚房:添加廚具");
        }
    }
5 客戶端測試:
     public void test() {
        Room newRoom = new NewRoom();//有一間新房間
        RoomDecorator bedroom = new Bedroom(newRoom);
        bedroom.fitment();//裝修成臥室
        RoomDecorator kitchen = new Kitchen(newRoom);
        kitchen.fitment();//裝修成廚房
    }

輸出結(jié)果:

這是一間新房:裝上電
裝修成臥室:添加臥具
這是一間新房:裝上電
裝修成廚房:添加廚具

廣泛應(yīng)用

我們都知道Activity咬腋、ServiceApplication等都是一個Context睡互,這里面實(shí)際上就是通過裝飾者模式來實(shí)現(xiàn)的根竿。下面以startActivity()這個方法來簡單分析一下。

1 Context類

Context實(shí)際上是個抽象類就珠,里面定義了大量的抽象方法寇壳,其中就包含了startActivity()方法:

    public abstract class Context {//抽象類
        public abstract void startActivity(@RequiresPermission Intent intent);//抽象方法
        //其他代碼略
    }  

2 ContextImpl類

Context類的具體實(shí)現(xiàn)實(shí)際上是在ContextImpl類,里面具體實(shí)現(xiàn)了startActivity()方法:

    class ContextImpl extends Context {
        @Override
        public void startActivity(Intent intent) {
            warnIfCallingFromSystemProcess();
            startActivity(intent, null);
        }

        @Override
        public void startActivity(Intent intent, Bundle options) {//具體實(shí)現(xiàn)原理這里就不分析了
            warnIfCallingFromSystemProcess();

            // Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
            // generally not allowed, except if the caller specifies the task id the activity should
            // be launched in.
            if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
                    && options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) {
                throw new AndroidRuntimeException(
                        "Calling startActivity() from outside of an Activity "
                                + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                                + " Is this really what you want?");
            }
            mMainThread.getInstrumentation().execStartActivity(
                    getOuterContext(), mMainThread.getApplicationThread(), null,
                    (Activity) null, intent, -1, options);
        }

        //其他代碼略
    }

3 ContextWrapper類

通常我們在Activity妻怎、Service里面調(diào)用startActivity()方法壳炎,實(shí)際上是調(diào)用他們的父類ContextWrapper里面的startActivity()方法,我們先來看下Activity逼侦、Service的繼承關(guān)系:

Activity的繼承關(guān)系
Service的繼承關(guān)系

可以看到Activity匿辩、Service都是繼承自ContextWrapper,再來看看ContextWrapper的代碼:

    public class ContextWrapper extends Context {//Context包裝類
        Context mBase;//持有Context引用

        public ContextWrapper(Context base) {//這里的base實(shí)際上就是ContextImpl
            mBase = base;
        }

        @Override
        public void startActivity(Intent intent) {
            mBase.startActivity(intent);//調(diào)用ContextImpl的startActivity()方法
        }

        //其他代碼略
    }

4 總結(jié)

Context類在這里就充當(dāng)了抽象組件的角色偿洁,ContextImpl類則是具體的組件撒汉,而ContextWrapper就是具體的裝飾角色沟优,通過擴(kuò)展ContextWrapper增加不同的功能涕滋,就形成了ActivityService等子類挠阁。最后账劲,放一張總的UML類圖幫助理解:

image

總結(jié)

裝飾模式 VS 繼承

  • 裝飾模式:對于一個給定的對象呻引,同時可能有不同的裝飾對象,客戶端可以通過它的需要選擇合適的裝飾對象發(fā)送消息。
  • 繼承:對于所有可能的聯(lián)合较剃,客戶期望很容易增加任何的 困難
裝飾模式 繼承
用來擴(kuò)展特定對象的功能 用來擴(kuò)展一類對象的功能
不需要子類 需要子類
動態(tài)地 靜態(tài)地
運(yùn)行時分配職責(zé) 編譯時分派職責(zé)
防止由于子類而導(dǎo)致的復(fù)雜和混亂 導(dǎo)致很多子類產(chǎn)生,在一些場合蝉稳,報(bào)漏類的層次
更多的靈活性 缺乏靈活性

總結(jié)一下裝飾者模式的優(yōu)缺點(diǎn)

優(yōu)點(diǎn)

采用組合的方式破婆,可以動態(tài)的擴(kuò)展功能,同時也可以在運(yùn)行時選擇不同的裝飾器寻歧,來實(shí)現(xiàn)不同的功能掌栅。
有效避免了使用繼承的方式擴(kuò)展對象功能而帶來的靈活性差,子類無限制擴(kuò)張的問題码泛。
被裝飾者與裝飾者解偶猾封,被裝飾者可以不知道裝飾者的存在,同時新增功能時原有代碼也無需改變噪珊,符合開放封閉原則晌缘。

缺點(diǎn)

裝飾層過多的話齐莲,維護(hù)起來比較困難。
如果要修改抽象組件這個基類的話磷箕,后面的一些子類可能也需跟著修改选酗,較容易出錯。

適用場景

需要擴(kuò)展一個類的功能搀捷,或給一個類增加附加功能時
需要動態(tài)的給一個對象增加功能星掰,這些功能可以再動態(tài)的撤銷
當(dāng)不能采用繼承的方式對系統(tǒng)進(jìn)行擴(kuò)充或者采用繼承不利于系統(tǒng)擴(kuò)展和維護(hù)時。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嫩舟,一起剝皮案震驚了整個濱河市氢烘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌家厌,老刑警劉巖播玖,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異饭于,居然都是意外死亡蜀踏,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進(jìn)店門掰吕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來果覆,“玉大人,你說我怎么就攤上這事殖熟【执” “怎么了?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵菱属,是天一觀的道長钳榨。 經(jīng)常有香客問我,道長纽门,這世上最難降的妖魔是什么薛耻? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮赏陵,結(jié)果婚禮上饼齿,老公的妹妹穿的比我還像新娘。我一直安慰自己蝙搔,他們只是感情好缕溉,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著杂瘸,像睡著了一般倒淫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天敌土,我揣著相機(jī)與錄音镜硕,去河邊找鬼。 笑死返干,一個胖子當(dāng)著我的面吹牛兴枯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播矩欠,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼财剖,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了癌淮?” 一聲冷哼從身側(cè)響起躺坟,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎乳蓄,沒想到半個月后咪橙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡虚倒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年美侦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片魂奥。...
    茶點(diǎn)故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡菠剩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出耻煤,到底是詐尸還是另有隱情具壮,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布违霞,位于F島的核電站嘴办,受9級特大地震影響瞬场,放射性物質(zhì)發(fā)生泄漏买鸽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一贯被、第九天 我趴在偏房一處隱蔽的房頂上張望眼五。 院中可真熱鬧,春花似錦彤灶、人聲如沸看幼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诵姜。三九已至,卻和暖如春搏熄,著一層夾襖步出監(jiān)牢的瞬間棚唆,已是汗流浹背暇赤。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宵凌,地道東北人鞋囊。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像瞎惫,于是被迫代替她去往敵國和親溜腐。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容