java/android設(shè)計模式-結(jié)構(gòu)模式之外觀模式


外觀模式(Facade Pattern)也叫門面模式,總覺得叫門面模式有點土吧雹,哈哈。雖然可能聽著不如單例模式涂身,觀察者模式來得熟悉雄卷,但是在實際開發(fā)中使用頻率還是很高的。特別是三方的SDK大概都會使用它访得。我們在平時的開發(fā)中可能已經(jīng)使用了外觀模式龙亲,只是還沒有從理論上來認識它。

外觀模式介紹

UML結(jié)構(gòu)圖

外觀模式?jīng)]有一個一般化的類圖悍抑,就用一個一般的結(jié)構(gòu)圖表示鳄炉。


外觀模式結(jié)構(gòu)圖

角色介紹

  • Client : 客戶端程序
  • Facade : 對外的統(tǒng)一入口,即外觀對象
  • System One : 子系統(tǒng)1(類的集合)
  • System Two : 子系統(tǒng)2(類的集合)
  • System Three: 子系統(tǒng) 3(類的集合)
  • System Four :子系統(tǒng) 4(類的集合)

特點

外觀模式提供一個統(tǒng)一的接口,用來訪問子系統(tǒng)中的一群接口搜骡,外觀定義了一個高層接口拂盯,讓子系統(tǒng)更容易使用。
使用場景:

  • 為一個復(fù)雜子系統(tǒng)提供一個簡單接口记靡。Facade 可以提供一個簡單統(tǒng)一的接口谈竿,對外隱藏子系統(tǒng)的具體實現(xiàn)、隔離變化摸吠。
  • 當需要構(gòu)建一個層次結(jié)構(gòu)的子系統(tǒng)時空凸,使用 Facade 模式定義子系統(tǒng)中每層的入口點。如果子系統(tǒng)之間是相互依賴寸痢,你可以讓他們僅通過 Facade 接口進行通信呀洲,從而簡化了它們之間的依賴關(guān)系。
    在上面外觀模式的結(jié)構(gòu)圖中如果沒有中間的外觀類的話,那么客戶端使用各個系統(tǒng)的功能會比較復(fù)雜道逗,需要和各個子系統(tǒng)進行交互兵罢,使得系統(tǒng)的穩(wěn)定性受到影響,用戶的使用成本也會相對高滓窍。

簡單實現(xiàn)例子

比如人要看電視卖词,現(xiàn)在都肯定不用十幾二十看大頭黑白電視那樣,想換臺了還要跑到電視面前去轉(zhuǎn)那個手動的電視換臺按鈕吏夯,聲音大了又要跑到電視面前去轉(zhuǎn)動聲音調(diào)節(jié)按鈕〈蓑冢現(xiàn)在都是用遙控器對吧。人就是這個客戶端噪生,電視是就什么電視臺系統(tǒng)舶替,音樂系統(tǒng)的集合,而遙控板就是那個外觀類杠园。

ChannelSystem簡化成一個類:

public class ChannelSystem {
    public void nextChannel() {
        System.out.println("下一個頻道");
    }

    public void prevChannel() {
        System.out.println("上一頻道");
    }
}

VoiceSystem簡化成一個類:

public class VoiceSystem {
    public void turnUp() {
        System.out.println("音量增大");
    }

    public void turnDown() {
        System.out.println("音量減小");
    }
}

電源系統(tǒng)簡化成一個類:

public class PowerSystem {
    public void powerOn() {
        System.out.println("開機");
    }

    public void powerOff() {
        System.out.println("關(guān)機");
    }
}

遙控板相當于外觀類:

public class TvController {
    private PowerSystem mPowerSystem = new PowerSystem();
    private VoiceSystem mVoiceSystem = new VoiceSystem();
    private ChannelSystem mChannelSystem = new ChannelSystem();

    public void powerOn() {
        mPowerSystem.powerOn();
    }

    public void powerOff() {
        mPowerSystem.powerOff();
    }

    public void turnUp() {
        mVoiceSystem.turnUp();
    }

    public void turnDown() {
        mVoiceSystem.turnDown();
    }

    public void nextChannel() {
        mChannelSystem.nextChannel();
    }

    public void prevChannel() {
        mChannelSystem.prevChannel();
    }
}

android中外觀模式的實現(xiàn)

在Android中Context類族是比較重要的,它提供了應(yīng)用環(huán)境信息的接口舔庶。我們可以通過它訪問到應(yīng)用資源和類抛蚁,以及進行一些系統(tǒng)級別的操作,像什么加載activity,發(fā)送廣播和接收Intent惕橙,Context類給我們提供了一站式服務(wù)瞧甩,這里就是應(yīng)用了外觀模式。
在應(yīng)用啟動時弥鹦,首先會fork一個子進程肚逸,并且調(diào)用ActivityThread.main方法啟動該進程。ActivityThread又會構(gòu)建Application對象彬坏,然后和Activity朦促、ContextImpl關(guān)聯(lián)起來,然后再調(diào)用Activity的onCreate栓始、onStart务冕、onResume函數(shù)使Activity運行起來。我們看看下面的相關(guān)代碼:

private final void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        // 代碼省略

        // 1幻赚、創(chuàng)建并且加載Activity禀忆,調(diào)用其onCreate函數(shù)
        Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            Bundle oldState = r.state;
            // 2、調(diào)用Activity的onResume方法落恼,使Activity變得可見
            handleResumeActivity(r.token, false, r.isForward);

        }
    }


     private final Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
        // 代碼省略

        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            // 1箩退、創(chuàng)建Activity
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            r.intent.setExtrasClassLoader(cl);
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }

        try {
            // 2、創(chuàng)建Application
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);

            if (activity != null) {
                // ***** 構(gòu)建ContextImpl  ****** 
                ContextImpl appContext = new ContextImpl();
                appContext.init(r.packageInfo, r.token, this);
                appContext.setOuterContext(activity);
                // 獲取Activity的title
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mConfiguration);

                 // 3佳谦、Activity與context, Application關(guān)聯(lián)起來
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstance,
                        r.lastNonConfigurationChildInstances, config);
                // 代碼省略

                // 4戴涝、回調(diào)Activity的onCreate方法
                mInstrumentation.callActivityOnCreate(activity, r.state);

                // 代碼省略
            }
            r.paused = true;

            mActivities.put(r.token, r);

        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {

        }

        return activity;
    }


    final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {

        unscheduleGcIdler();

        // 1、最終調(diào)用Activity的onResume方法
        ActivityClientRecord r = performResumeActivity(token, clearHide);
        // 代碼省略
        // 2、這里是重點喊括,在這里使DecorView變得可見
        if (r.window == null && !a.mFinished && willBeVisible) {
                // 獲取Window胧瓜,即PhoneWindow類型
                r.window = r.activity.getWindow();
                // 3、獲取Window的頂級視圖郑什,并且使它可見
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                // 4府喳、獲取WindowManager
                ViewManager wm = a.getWindowManager();
                // 5、構(gòu)建LayoutParams參數(shù)
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    // 6蘑拯、將DecorView添加到WindowManager中钝满,最終的操作是通過WindowManagerService的addView來操作
                    wm.addView(decor, l);
                }
            } else if (!willBeVisible) {
                if (localLOGV) Slog.v(
                    TAG, "Launch " + r + " mStartedActivity set");
                r.hideForNow = true;
            }
            // 代碼省略
    }

 public final ActivityClientRecord performResumeActivity(IBinder token,
            boolean clearHide) {
        ActivityClientRecord r = mActivities.get(token);

        if (r != null && !r.activity.mFinished) {
                try {
                // 代碼省略
                // 執(zhí)行onResume
                r.activity.performResume();
                // 代碼省略
            } catch (Exception e) {

            }
        }
        return r;
    }

Activity啟動之后,Android給我們提供了操作系統(tǒng)服務(wù)的統(tǒng)一入口申窘,也就是Activity本身弯蚜。這些工作并不是Activity自己實現(xiàn)的,而是將操作委托給Activity父類ContextThemeWrapper的mBase對象剃法,這個對象的實現(xiàn)類就是ContextImpl ( 也就是performLaunchActivity方法中構(gòu)建的ContextImpl )碎捺。

class ContextImpl extends Context {
    private final static String TAG = "ApplicationContext";
    private final static boolean DEBUG = false;
    private final static boolean DEBUG_ICONS = false;

    private static final Object sSync = new Object();
    private static AlarmManager sAlarmManager;
    private static PowerManager sPowerManager;
    private static ConnectivityManager sConnectivityManager;
    private AudioManager mAudioManager;
    LoadedApk mPackageInfo;
    private Resources mResources;
    private PackageManager mPackageManager;
    private NotificationManager mNotificationManager = null;
    private ActivityManager mActivityManager = null;

    // 代碼省略

        @Override
    public void sendBroadcast(Intent intent) {
        String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
        try {
            ActivityManagerNative.getDefault().broadcastIntent(
                mMainThread.getApplicationThread(), intent, resolvedType, null,
                Activity.RESULT_OK, null, null, null, false, false);
        } catch (RemoteException e) {
        }
    }


        @Override
    public void startActivity(Intent intent) {
        if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
            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, null, intent, -1);
    }


        @Override
    public ComponentName startService(Intent service) {
        try {
            ComponentName cn = ActivityManagerNative.getDefault().startService(
                mMainThread.getApplicationThread(), service,
                service.resolveTypeIfNeeded(getContentResolver()));
            if (cn != null && cn.getPackageName().equals("!")) {
                throw new SecurityException(
                        "Not allowed to start service " + service
                        + " without permission " + cn.getClassName());
            }
            return cn;
        } catch (RemoteException e) {
            return null;
        }
    }

        @Override
    public String getPackageName() {
        if (mPackageInfo != null) {
            return mPackageInfo.getPackageName();
        }
        throw new RuntimeException("Not supported in system context");
    }
}

可以看到,ContextImpl內(nèi)部有很多xxxManager類的對象贷洲,也就是我們上文所說的各種子系統(tǒng)的角色收厨。ContextImpl內(nèi)部封裝了一些系統(tǒng)級別的操作,有的子系統(tǒng)功能雖然沒有實現(xiàn)优构,但是也提供了訪問該子系統(tǒng)的接口诵叁,比如獲取ActivityManager的getActivityManager方法。
比如我們要啟動一個Activity的時候钦椭,我們調(diào)用的是startActivity方法拧额,這個功能的內(nèi)部實現(xiàn)實際上是Instrumentation完成的。ContextImpl封裝了這個功能彪腔,使得用戶根本不需要知曉Instrumentation相關(guān)的信息侥锦,直接使用startActivity即可完成相應(yīng)的工作。其他的子系統(tǒng)功能也是類似的實現(xiàn)漫仆,比如啟動Service和發(fā)送廣播內(nèi)部使用的是ActivityManagerNative等捎拯。ContextImpl的結(jié)構(gòu)圖如下 :

優(yōu)缺點

優(yōu)點:

對客戶程序隱藏子系統(tǒng)細節(jié),因而減少了客戶對于子系統(tǒng)的耦合盲厌,能夠擁抱變化署照;
外觀類對子系統(tǒng)的接口封裝,使得系統(tǒng)更易于使用吗浩;
更好的劃分訪問層次建芙,通過合理使用Facade,可以幫助我們更好地劃分訪問的層次懂扼。有些方法是對系統(tǒng)外的禁荸,有些方法是系統(tǒng)內(nèi)部使用的右蒲。把需要暴露給外部的功能集中到外觀類中,這樣既方便客戶端使用赶熟,也很好地隱藏了內(nèi)部的細節(jié)瑰妄。

缺點:

外觀類接口膨脹,由于子系統(tǒng)的接口都由外觀類統(tǒng)一對外暴露映砖,使得外觀類的 API 接口較多间坐,在一定程度上增加了用戶使用成本;
外觀類沒有遵循開閉原則(對擴展開放邑退,對修改關(guān)閉)竹宋,當業(yè)務(wù)出現(xiàn)變更時,可能需要直接修改外觀類地技。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蜈七,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子莫矗,更是在濱河造成了極大的恐慌飒硅,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件作谚,死亡現(xiàn)場離奇詭異狡相,居然都是意外死亡,警方通過查閱死者的電腦和手機食磕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來喳挑,“玉大人彬伦,你說我怎么就攤上這事∫了校” “怎么了单绑?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長曹宴。 經(jīng)常有香客問我搂橙,道長,這世上最難降的妖魔是什么笛坦? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任区转,我火速辦了婚禮,結(jié)果婚禮上版扩,老公的妹妹穿的比我還像新娘废离。我一直安慰自己,他們只是感情好礁芦,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布蜻韭。 她就那樣靜靜地躺著悼尾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肖方。 梳的紋絲不亂的頭發(fā)上闺魏,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天,我揣著相機與錄音俯画,去河邊找鬼析桥。 笑死,一個胖子當著我的面吹牛活翩,可吹牛的內(nèi)容都是我干的烹骨。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼材泄,長吁一口氣:“原來是場噩夢啊……” “哼沮焕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拉宗,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤峦树,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后旦事,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體魁巩,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年姐浮,在試婚紗的時候發(fā)現(xiàn)自己被綠了谷遂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡卖鲤,死狀恐怖肾扰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蛋逾,我是刑警寧澤集晚,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站区匣,受9級特大地震影響偷拔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜亏钩,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一莲绰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧姑丑,春花似錦钉蒲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽踏枣。三九已至,卻和暖如春钙蒙,著一層夾襖步出監(jiān)牢的瞬間茵瀑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工躬厌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留马昨,地道東北人。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓扛施,卻偏偏與公主長得像鸿捧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子疙渣,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

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