模式的定义
门面模式(Facade Pattern)也叫做外观模式,定义如下:
Provide a unified interface to a set of interfaces in a subsystem. Facade defines a highet-level interface that makes the subsystem easier to use.
要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。
类型
结构类
模式的使用场景
- 为一个复杂的模块或子系统提供一个借外界访问的接口
- 子系统相对独立—外界对子系统的访问只要黑箱操作即可
比如利息的计算问题,比较复杂并且是一个动态的变化过程,但是对于使用该系统的人员来说,他只需要输入金额和存期,其它的不想关心,就需要得到一个结果。这时,门面模式就可以只提供一个访问接口就得到结果。非常合适。
- 预防低水平人员带来的风险扩散
比如一个低水平的技术人员参与项目的开发,为了降低个人代码对整体项目的影响风险,一般的做法是画地为牢,中能在指定的子系统中开发,然后再提供门面接口进行访问操作。
UML类图
门面模式注重统一的对象,也就是提供一个访问子系统的接口,除了这个接口,不允许有任何访问子系统的行为发生。其通用类图的uml如下:
Subsystem classes是子系统所有类的简称,它可能代表一个类,也可能代表几十个对象的集合。再简单的说,门面系统中门面对象是外界访问子系统内部的唯一通道,不管子系统内部是多么的杂乱无章,只要有门面对象在,就可以做到”金玉其外,败絮其中“。
门面模式的示意图如下:
角色介绍
Facade门面角色
客户端可以调用这个角色的方法。此角色知晓子系统的所有功能和责任。一般情况下,本角色会将所有从客户端发来的请求委派到相应的子系统去,也就是说该角色没有实际的业务逻辑,只是一个委托类。
subsystem classes子系统角色
可以同时有一个或者多个子系统。每一个子系统都不是一个单独类,而是一个类的集合。子系统不知道门面的存在。对于子系统而言,门面仅仅是另外一个客户端而已。
模式的通用源码
subsystem classes 子系统:
public class ClassA {
public void doSomethingA(){
System.out.println("ClassA---doSomethingA");
}
}
public class ClassB {
public void doSomethingB(){
System.out.println("ClassB---doSomethingB");
}
}
public class ClassC {
public void doSomethingC(){
System.out.println("ClassC---doSomethingC");
}
}
Facade–门面对象:
public class Facade {
private ClassA a = new ClassA();
private ClassB b = new ClassB();
private ClassC c = new ClassC();
public void methodA(){
this.a.doSomethingA();
}
public void methodB(){
this.b.doSomethingB();
}
public void methodC(){
this.c.doSomethingC();
}
}
客户端:
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
Facade facade = new Facade();
facade.methodA();
facade.methodB();
facade.methodC();
}
}
输出结果:
ClassA---doSomethingA
ClassB---doSomethingB
ClassC---doSomethingC
优点
- 减少系统的相互依赖
想想看,如果我们不使用门面模式,外界访问直接深入到子系统内部,相互之间是一种强耦合关系,互相互相依赖和关联,是系统设计所不能接受的,门面模式的出现很好的解决了此问题,所有的依赖都是对门面对象的依赖,与子系统无关。
- 提高了系统的灵活性
依赖减少了,灵活性就自然提高了。不管子系统如何变化,只要不影响门面对象,就可以自由修改。
- 提高安全性
想让你访问子系统中哪些业务就开通哪些逻辑,不在门面对象上开通,我们就不能访问,安全性自然提高了。
缺点
门面模式最大的缺点是不符合开闭原则,对修改关闭,对扩展开放。门面模式中门面对象是重中之重,一旦系统投产生,如果发现了有错误,你怎么解决?完全遵从开闭原型,根本没有办法解决。继承?覆写?都用不上。唯一能做的就是修改门面对象的代码,这个风险是相当大的。请大家要注意。
注意事项
- 一个子系统可以有多个门面
- 门面不参考子系统内的业务逻辑
Android源码中的模式实现
门面模式作为结构类设计模式,其作用我们可以理解为一个对用户公开的接口,非常方便用户直接调用其内部方法以实现对所有的子系统(也就是其它的类)的访问和调用。也就是说用户经常使用一个统一的接口来实现对其他的类的访问和调用。
那么android开发时,我们是不是有这样的情况了?
我们是不是在开发时,经常有下面的操作:
使用一个Context对象来发送广播—使用门面对象Context的sendBroadcast方法来实现对ActivityManagerNative类的访问。
使用一个Context对象来启动服务—使用门面对象Context的startService方法来实现对ActivityManagerNative类的访问。
使用一个Context对象来启动activity—使用门面对象Context的startActivity方法来实现对mMainThread.getInstrumentation()的访问。
使用一个Context对象来获取PackageManager信息—使用门面对象Context的getPackageManager()方法来实现的ApplicationPackageManager类的访问。
等等.
也就是说,android开发时的门面模式的样例是Context类。
我们查看Context类:
public abstract class Context {
......
public abstract AssetManager getAssets();
/** Return a Resources instance for your application‘s package. */
public abstract Resources getResources();
/** Return PackageManager instance to find global package information. */
public abstract PackageManager getPackageManager();
/** Return a ContentResolver instance for your application‘s package. */
public abstract ContentResolver getContentResolver();
......
public abstract void sendBroadcast(Intent intent);
public abstract ComponentName startService(Intent service);
public abstract void startActivity(Intent intent);
......
}
从代码来看,Context 只是一个抽象类,定义了许多抽象接口和定量。其它实现类主要有二个:ContextImpl和ContextWrapper。
先看ContextImpl类,其继承抽象类Context,实现其具体方法,充当一个门面对象的作用,方便用户对所有的三方类访问。
class ContextImpl extends Context {
final ActivityThread mMainThread;
final LoadedApk mPackageInfo;
private final IBinder mActivityToken;
private final UserHandle mUser;
private final ApplicationContentResolver mContentResolver;
private final String mBasePackageName;
private final String mOpPackageName;
private final ResourcesManager mResourcesManager;
private final Resources mResources;
private final Display mDisplay; // may be null if default display
private final DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments();
private final boolean mRestricted;
private Context mOuterContext;
private int mThemeResource = 0;
private Resources.Theme mTheme = null;
private PackageManager mPackageManager;
private Context mReceiverRestrictedContext = null;
......
//获取具体的Context
static ContextImpl getImpl(Context context) {
Context nextContext;
while ((context instanceof ContextWrapper) &&
(nextContext=((ContextWrapper)context).getBaseContext()) != null) {
context = nextContext;
}
return (ContextImpl)context;
}
//获取AssetManager
@Override
public AssetManager getAssets() {
return getResources().getAssets();
}
//获取Resources
@Override
public Resources getResources() {
return mResources;
}
//获取PackageManager
@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;
}
//获取ContentResolver
@Override
public ContentResolver getContentResolver() {
return mContentResolver;
}
//获取Looper
@Override
public Looper getMainLooper() {
return mMainThread.getLooper();
}
//获取Context
@Override
public Context getApplicationContext() {
return (mPackageInfo != null) ?
mPackageInfo.getApplication() : mMainThread.getApplication();
}
......
//发送广播
public void sendBroadcast(Intent intent) {
warnIfCallingFromSystemProcess();
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
intent.prepareToLeaveProcess();
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false,
getUserId());
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
}
//启动服务的操作实现方法,关键是调用 ActivityManagerNative.getDefault().startService
private ComponentName startServiceCommon(Intent service, UserHandle user) {
try {
validateServiceIntent(service);
service.prepareToLeaveProcess();
ComponentName cn = ActivityManagerNative.getDefault().startService(
mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
getContentResolver()), getOpPackageName(), user.getIdentifier());
if (cn != null) {
if (cn.getPackageName().equals("!")) {
throw new SecurityException(
"Not allowed to start service " + service
+ " without permission " + cn.getClassName());
} else if (cn.getPackageName().equals("!!")) {
throw new SecurityException(
"Unable to start service " + service
+ ": " + cn.getClassName());
}
}
return cn;
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
}
//启动activity,关键是调用方法mMainThread.getInstrumentation().execStartActivity
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
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,
(Activity) null, intent, -1, options);
}
......
}
再查看ContextWrapper 类:
public class ContextWrapper extends Context {
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
......
public Context getBaseContext() {
return mBase;
}
@Override
public AssetManager getAssets() {
return mBase.getAssets();
}
......
}
其中所有的方法,只是简单的调用mBase 对应的方法就可以。
再来二张门面模式的示意图,大家看一眼就明白了。
门面模式(ContextImpl 和ContextWrapper),确实是充当了一个门面,其真正的价值是提供一个访问内部系统类的一个统一接口,非常方便用户来访问系统内部,。作为结构类设计模式,这确实是一个在所有类的充当门面的结构类。
参考资料
(1).设计模式之禅—第23章 门面模式
(2)门面模式