1.外观模式
外观模式也叫门面模式、Facade模式,是一种结构型设计模式。要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。外观模式提供一个统一的高层次的接口,使得子系统更容易使用。
外观模式就是通过一个统一的接口对外提供服务,使得外部程序只通过一个类就可以实现系统内部的多种功能。
一般第三方SDK经常使用外观模式,通过一个外观类使得整个系统的接口只有一个统一的高层接口,降低用户的使用成本,也对用户屏蔽很多实现细节。
使用场景:
①为一个复杂的子系统提供一个简单接口,对外隐藏子系统的具体实现、隔离变化;
②当需要构建一个层次结构的子系统时,使用外观模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,就可以让它们仅通过Facade接口进行通信,从而简化它们之间的依赖关系;
③客户程序与多个子系统之间存在很大的依赖性;
UML类图:
外观类Facade:系统对外的统一接口,客户端连接子系统功能的入口。
子系统SubSystem:可以同时有一个或多个子系统,每个子系统都不是一个单独的类,而是一个类的集合。每个子系统都可以被客户端直接调用或者被门面角色调用。子系统并不知道门面的存在,对于子系统而言,门面仅仅是另外一个客户端而已。
外观模式是一个高频率使用的设计模式,它的精髓在于封装二字。通过一个高层次结构为用户提供统一的API入口,使得用户通过一个类型就基本能够操作整个系统,从而减少了用户的使用成本,也能够提升系统的灵活性。
优点:
①降低客户类与子系统的耦合度,实现松耦合;
②对子系统的接口封装,对客户程序隐藏子系统细节,从而简化了接口,减少客户处理的对象数据并使子系统的使用更加简单。
③使用外观模式可以将一个子系统和使用它的客户端以及自己其他子系统分离开来,这样就提高了子系统的独立性和可移植性。
缺点:
①外观类接口复杂。由于子系统的接口都由外观类统一对外暴露,使得外观类的API接口较多,在一定程度上增加了用户使用成本。
②增加新的子系统可能需要修改外观类的源代码,违背了开闭原则。
外观模式和适配器模式的区别:
适配器模式是将一个对象包装起来以改变其接口,而外观模式是将一群对象包装起来以简化其接口。它们的意图是不同的。
2.外观模式理解
先通过一个简单的例子来了解外观模式。其实手机可以看作是一个外观类,而手机中的功能,比如打电话、相机则是各种子模块。
①子系统:打电话的功能
public interface Phone {
public void dail();//打电话
public void hangup();//挂断
}
//电话的实现类
public class PhoneImpl implements Phone {
@Override
public void dail() {
System.out.println("打电话");
}
@Override
public void hangup() {
System.out.println("挂断");
}
}
②子系统:相机的功能
public interface Camera {
public void open();//打开相机
public void takePhoto();//拍照
public void close(); //关闭相机
}
//相机功能的实现类
public class MiCamera implements Camera {
@Override
public void open() {
System.out.println("打开相机");
}
@Override
public void takePhoto() {
System.out.println("拍照");
}
@Override
public void close() {
System.out.println("关闭相机");
}
}
③Facade类:手机
public class MobilePhone {
private Phone mPhone = new PhoneImpl();
private Camera mCamera = new MiCamera();
//拍照
public void takePhoto() {
mCamera.open();
mCamera.takePhoto();
mCamera.close();
}
//视频聊天
public void videoChat() {
mCamera.open();
mPhone.dail();
}
}
使用手机时需要用到拍照和视频聊天的功能,但我们不需要知道相机的信息,也不需要知道视频聊天要用到了哪些类、Phone的接口和实现等,只需要用到手机MobilePhone这个类和它提供的接口takePhoto和videoChat:
MobilePhone mobilePhone = new MobilePhone();
mobilePhone.takePhoto();
mobilePhone.videoChat();
3.Android源码中的应用
Android源码中,Context就是外观模式的一个例子。Context这个抽象类定义了很多方法,它的具体实现都在ContextImpl.java这个类里。
class ContextImpl extends Context {
@Override
public void startActivity(Intent intent, Bundle options) {
...
mMainThread.getInstrumentation().exe cStartActivity(getOuterContext(),mMainThread.getApplicationThread(), null, (Activity) null, intent, -1, options);
}
@Override
public ComponentName startService(Intent service) {
warnIfCallingFromSystemProcess();
return startServiceCommon(service, false, mUser);
}
private ComponentName startServiceCommon(Intent service, boolean requireForeground,UserHandle user) {
try {
validateServiceIntent(service);
service.prepareToLeaveProcess(this);
ComponentName cn = ActivityManager.getService().startService(mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(getContentResolver()), requireForeground, getOpPackageName(), getAttributionTag(), user.getIdentifier());
...
}
return cn;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@Override
public void sendBroadcast(Intent intent) {
warnIfCallingFromSystemProcess();
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
intent.prepareToLeaveProcess(this);
ActivityManager.getService().broadcas tIntentWithFeature(mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType, null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false, getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
可以看到ContextImpl封装了startActivity、startService、sendBroadcast一系列核心方法,而各个方法内部又是通过调用其它类来实现的,很明显是外观模式。
注:Retrofit、Glide源码都使用了外观模式,Retrofit和Glide使用起来非常简单,但是它们的内部却很复杂。