外觀模式(Facade Pattern)也叫門面模式,總覺得叫門面模式有點土吧雹,哈哈。雖然可能聽著不如單例模式涂身,觀察者模式來得熟悉雄卷,但是在實際開發(fā)中使用頻率還是很高的。特別是三方的SDK大概都會使用它访得。我們在平時的開發(fā)中可能已經(jīng)使用了外觀模式龙亲,只是還沒有從理論上來認識它。
外觀模式介紹
UML結(jié)構(gòu)圖
外觀模式?jīng)]有一個一般化的類圖悍抑,就用一個一般的結(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)變更時,可能需要直接修改外觀類地技。