22 设计模式——外观模式

返回设计模式博客目录

介绍


外观(Facade)模式:要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。外观模式提供一个高层次的接口,使得子系统更易于使用

在开发过程中的运用频率非常高,尤其是在现阶段各种第三方 SDK 充斥在我们的周边,而这些 SDK 很大概率会使用外观模式。通过一个外观类使得整个系统的接口只有一个统一的高层接口,这样能够降低用户的使用成本,也对用户屏蔽了很多实现细节。当然,在我们的开发过程中,外观模式也是我们封装 API 的常用手段,例如网络模块、ImageLoader模块等。

优点

外观模式是“迪米特法则”的典型应用,主要优点如下:

  • 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
  • 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
  • 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。

缺点

  • 不能很好地限制客户使用子系统类。
  • 增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

使用场景

  • 为一个复杂的子系统提供一个简单接口,对外隐藏子系统的具体实现、隔离变化。
  • 使用外观模式可以将一个子系统和使用它的客户端以及其它的子系统分离开来,这就提高了子系统的独立性和可移植性。
  • 在构建一个层次化结构的时候,可以使用外观模式定义每一个层次对外交互的接口。这样,层与层之间只需要通过外观进行通信,从而简化层与层之间的依赖关系。

结构与实现


模式包含以下主要角色。

  • Facade(外观角色):对外的统一入口。
  • Sub System(子系统):一般由多个子系统构成,负责具体功能的实现。

其结构图如下图所示。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class FacadePattern {
public static void main(String[] args) {
Facade f = new Facade();
f.method();
}
}
// 外观角色
class Facade {
private SubSystem01 obj1 = new SubSystem01();
private SubSystem02 obj2 = new SubSystem02();
private SubSystem03 obj3 = new SubSystem03();
public void method() {
obj1.method1();
obj2.method2();
obj3.method3();
}
}
// 子系统角色
class SubSystem01 {
public void method1() {
System.out.println("子系统01的method1()被调用!");
}
}
// 子系统角色
class SubSystem02 {
public void method2() {
System.out.println("子系统02的method2()被调用!");
}
}
// 子系统角色
class SubSystem03 {
public void method3() {
System.out.println("子系统03的method3()被调用!");
}
}

示例


手机就是一个外观模式的例子,它集合了电话功能、短信功能、GPS、拍照等于一身,通过手机你就可以完成各种功能。如下图所示:

下面模拟手机的外观模式实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public interface Camera {
void open();
void takePic();
void close();
}
public class SamsungCamera implements Camera {
@Override
public void open() {
System.out.println("打开相机");
}
@Override
public void takePic() {
System.out.println("拍照");
}
@Override
public void close() {
System.out.println("关闭相机");
}
}
public interface Phone {
// 打电话
void dail();
// 挂断
void hangup();
}
public class PhoneImpl implements Phone{
@Override
public void dail() {
System.out.println("打电话");
}
@Override
public void hangup() {
System.out.println("挂断");
}
}
// 外观类
public class MobilePhone {
private Phone mPhone = new PhoneImpl();
private Camera mCamera = new SamsungCamera();
public void dail(){
mPhone.dail();
}
public void videoChat(){
System.out.println("-->视频聊天接通中");
mCamera.open();
mPhone.dail();
}
public void hangup(){
mPhone.hangup();
}
public void takePic(){
mCamera.open();
mCamera.takePic();
}
public void closeCamera(){
mCamera.close();
}
}

测试代码:

1
2
3
4
5
6
7
8
9
public class Test {
public static void main(String[] args){
MobilePhone nexus6 = new MobilePhone();
// 拍照
nexus6.videoChat();
// 拍照
nexus6.takePic();
}
}

ANDROID 源码中的实现


在用 Android 开发过程中,Context 是最重要的一个类型。Context 只是一个抽象类,它的真正实现在 ContextImpl 类中,ContextImpl 就是今天我们要分析的外观类。

在应用启动时,首先会 fork 一个子进程,并且调用 ActivityThread 的 main 方法启动该进程。ActivityThread 又会构建 Application 对象,然后和 Activity、ContextImpl 关联起来,最后会调用 Activity 的 onCreate、onStart、onResume 函数使 Activity 运行起来,此时应用的用户界面就呈现在我们面前。

main 函数会间接地调用 ActivityThread 中的 handleLaunchActivity 函数启动默认的 Activity。handleLaunchActivity 方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Override
public Activity handleLaunchActivity(ActivityClientRecord r,
PendingTransactionActions pendingActions,
Intent customIntent) {
...
// 创建并且加载 Activity,调用它的 onCreate
final Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
reportSizeConfigurations(r);
if (!r.activity.mFinished && pendingActions != null) {
pendingActions.setOldState(r.state);
pendingActions.setRestoreInstanceState(true);
pendingActions.setCallOnPostCreate(true);
}
} else {
// If there was an error, for any reason, tell the activity manager to stop us.
try {
ActivityManager.getService()
.finishActivity(r.token, Activity.RESULT_CANCELED, null,
Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
return a;
}

performLaunchActivity 方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
// 构建 ContextImpl
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
// 创建 Activity
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
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 {
// 创建 Application
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
if (localLOGV) Slog.v(
TAG, r + ": app=" + app
+ ", appName=" + app.getPackageName()
+ ", pkg=" + r.packageInfo.getPackageName()
+ ", comp=" + r.intent.getComponent().toShortString()
+ ", dir=" + r.packageInfo.getAppDir());
if (activity != null) {
// 获取 Activity 的 title
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (r.overrideConfig != null) {
config.updateFrom(r.overrideConfig);
}
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
+ r.activityInfo.name + " with config " + config);
Window window = null;
if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
}
appContext.setOuterContext(activity);
// Activity 与 Context、Application 关联起来
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
if (customIntent != null) {
activity.mIntent = customIntent;
}
r.lastNonConfigurationInstances = null;
checkAndBlockForNetworkAccess();
activity.mStartedActivity = false;
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme);
}
activity.mCalled = false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
// 回调 Activity 的 onCreate 方法
mInstrumentation.callActivityOnCreate(activity, r.state);
}
if (!activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onCreate()");
}
r.activity = activity;
}
r.setState(ON_CREATE);
mActivities.put(r.token, r);
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to start activity " + component
+ ": " + e.toString(), e);
}
}
return activity;
}

在 handleLaunchActivity 中会调用 perfromLaunchActivity 执行 Applicaton、ContextImpl、Activity 的创建工作,并且通过 Activity 的 attach 将这 3 者关联起来。

Activity 是 Context 的子类,因此,Activity 就具有了 Context 定义的所有方法。但 Activity 并不实现具体的功能,它只是继承了 Context 的接口,并且将相关的操作交给 ContextImpl。ContextImpl 存储在 Activity 的上两层父类 ContextWrapper 中,变量名为 mBase,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class ContextThemeWrapper extends ContextWrapper {
...
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
}
...
}
public class ContextWrapper extends Context {
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
/**
* Set the base context for this ContextWrapper. All calls will then be
* delegated to the base context. Throws
* IllegalStateException if a base context has already been set.
*
* @param base The new base context for this wrapper.
*/
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
...
}

在 ActivityThread 类的 perfromLaunchActivity 函数中会调用 Activity 的 attach 方法将 ContextImpl 等对象关联到 Activity 中,这个 ContextImpl 最终会被 ContentWrapper 类的 mBase 字段引用。Activity 的 attach 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
// 调用自身的 attachBaseContext
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
mUiThread = Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mIdent = ident;
mApplication = application;
mIntent = intent;
mReferrer = referrer;
mComponent = intent.getComponent();
mActivityInfo = info;
mTitle = title;
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
if (voiceInteractor != null) {
if (lastNonConfigurationInstances != null) {
mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
} else {
mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
Looper.myLooper());
}
}
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
mWindow.setColorMode(info.colorMode);
setAutofillCompatibilityEnabled(application.isAutofillCompatibilityEnabled());
enableAutofillCompatibilityIfNeeded();
}

attach 主要就是一些赋值操作。在 attach 中,调用了 attachBaseContext 函数。attachBaseContext 调用了父类 ContextWrapper 类,它就是简单地将 Context 参数传递给 mBase 字段。此时,我们的 Activity 内部就持有了 ContextImpl 的引用。

Activity 的 attachBaseContext:

1
2
3
4
5
6
7
8
@Override
protected void attachBaseContext(Context newBase) {
// 调用了 ContextThemeWrapper 的 attachBaseContext
super.attachBaseContext(newBase);
if (newBase != null) {
newBase.setAutofillClient(this);
}
}

Activity 在开发过程中部分充当了代理的角色,例如,当我们通过 Activity 对象调用 sendBroadcast、getResource 等函数时,实际上 Activity 只是代理了 ContextImpl 的操作,也就是内部都调用了 mBase 对象的相应方法来处理,这些方法被封装在 Activity 的父类 ContextWrapper 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ContextWrapper extends Context {
Context mBase;
...
@Override
public void sendBroadcast(Intent intent) {
mBase.sendBroadcast(intent);
}
...
@Override
public Resources getResources() {
return mBase.getResources();
}
@Override
public PackageManager getPackageManager() {
return mBase.getPackageManager();
}
...
}

ContextImpl 的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class ContextImpl extends Context {
...
@Override
public void sendBroadcast(Intent intent) {
warnIfCallingFromSystemProcess();
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
intent.prepareToLeaveProcess(this);
ActivityManager.getService().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false,
getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
...
@Override
public Resources getResources() {
return mResources;
}
@Override
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageManager;
}
IPackageManager pm = ActivityThread.getPackageManager();
if (pm != null) {
// Doesn't matter if we make more than one instance.
return (mPackageManager = new ApplicationPackageManager(this, pm));
}
return null;
}
...
}

ContextImpl 内部封装了很多不同子系统的操作,例如,Activity 的跳转、发送广播、启动服务、设置壁纸等,这些工作并不是在 ContextImpl 中实现,而是转交给了具体的子系统进行处理。通过 Context 这个抽象类定义了一组接口,ContextImpl 实现 Context 定义的接口,使得用户可以通过 Context 这个接口统一与 Android 系统进行交互,这样用户通常情况下就不需要对每个子系统进行了解,例如启动 Activity 时用户不需要手动调用 mMainThread.getInstrumentation().execStartActivity 启动 Activity。用户与系统服务的交互都通过 Context 的高层接口。这样对用户屏蔽了具体实现的细节,降低了使用成本。