Retrofit源码设计模式解析
第一部分
Retrofit通过注解的方法标记HTTP请求参数,支持常用HTTP方法,统一返回值解析,支持异步/同步的请求方式,将HTTP请求对象化,参数化。真正执行网络访问的是Okhttp,Okhttp支持HTTP&HTTP2,因此,使用Retrofit可以支持REST、HTTPS及SPDY。
本文主要从设计模式的角度分享对Retrofit源码的一些理解。
外观模式
建造者模式
代理模式
简单工厂模式
工厂模式
抽象工厂模式
一、外观模式
在封装某些特定功能的子系统时,外观模式是一种很好的设计规范。即该子系统的外部与内部通信时通过一个统一的对象进行。Retrofit是整个库的一个入口类,Retrofit库的使用基本都是围绕着这个类。外观模式具有高内聚、低耦合的特性,对外提供简单统一的接口,隐蔽了子系统具体的实现、隔离变化。
Retrofit的外观模式的UML类图如下所示。
image
Retrofit对客户端模块(Client1、Client2……)提供统一接口,Retrofit类内部封装了ServiceMethod、CallAdapter和Converter等组件。并且,CallAdapter和Converter都是抽象为接口,用户可以扩展自定义的实现。正如官方文档中的示例:
Retrofitretrofit=newRetrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
附:举个栗子,说明下外观模式在封装条形码/二维码扫描功能时的应用。
Android中的条形码/二维码扫描功能通常会基于zxing库进行封装,定义CaptureActivity,统一提供扫码功能,并返回扫描结果。客户端使用该封装只需两步:首先,通过Intent启动CaptureActivity;然后,在onActivityResult中处理扫描结果。
Intentintent=newIntent(MainActivity.this,CaptureActivity.class);
startActivityForResult(intent,REQUESTCODE);
if(requestCode==REQUESTCODE&&resultCode==RESULT_OK){
Bundlebundle=data.getExtras();
StringscanResult=bundle.getString(CaptureActivity.RESULT);
helloWorld.setText(scanResult);
}
客户端不需要处理任何跟摄像头控制、调焦、图片处理、条形码解析等相关的问题。CaptureActivity提供了所有扫描相关的功能。
二、建造者模式
设计模式分为三种类型:创建型模式、结构型模式和行为型模式。建造者模式属于创建型模式,将构建复杂对象的过程和它的部件解耦,使构建过程和部件的表示隔离。Retrofit内部包含Retrofit.Builder,Retrofit包含的域都能通过Builder进行构建。经典设计模式(《设计模式:可复用面向对象软件的基础》)中建造者模式有四种角色:
Product产品类——该类为一般为抽象类,定义Product的公共属性配置;
Builder建造类——该类同样为抽象类,规范Product的组建,一般由子类实现具体Product的构建过程;
ConcreteBuilder实际建造类——继承自Builder,构建具体的Product;
Director组装类——统一组装过程。
在Retrofit类中,Retrofit直接对应Product,并没有基于抽象Product进行扩展;Retrofit.Builder对应ConcreteBuilder,也没有基于抽象Builder进行扩展,同时省略了Director,并在Retrofit.Builder每个setter方法都返回自身,使得客户端代码可以链式调用,整个构建过程更加简单。
Retrofitretrofit=newRetrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
举个栗子:笔者基于Retrofit封装适合自身业务的库时,由于需要配置基础URL、默认超时时间、拦截器以及是否添加转换器等,也采用相同的建造者模式。
复制代码
publicclassNetWork{
//基础URL设置
privateStringbaseUrl;
//默认超时时间
privatelongtimeout;
/
NetWork构建者
/
publicstaticclassBuilder{
//基础URL设置
privateStringbaseUrl;
//默认超时时间
privatelongtimeout=5;
/
baseUrl为必填项
@parambaseUrl基础Url
/
publicBuilder(StringbaseUrl){
this.baseUrl=baseUrl;
}
publicBuildertimeout(longtimeout){
this.timeout=timeout;
returnthis;
}
publicNetWorkbuild(){
returnnewNetWork(this);
}
}
/
构造器
@parambuilder构造builder
/
privateNetWork(Builderbuilder){
this.baseUrl=builder.baseUrl;
this.timeout=builder.timeout;
}
复制代码
三、代理模式
代理模式属于上述提到的结构型模式。当无法或不想直接访问某个对象,或者访问某个对象比较复杂的时候,可以通过一个代理对象来间接访问,代理对象向客户端提供和真实对象同样的接口功能。经典设计模式中,代理模式有四种角色:
Subject抽象主题类——申明代理对象和真实对象共同的接口方法;
RealSubject真实主题类——实现了Subject接口,真实执行业务逻辑的地方;
ProxySubject代理类——实现了Subject接口,持有对RealSubject的引用,在实现的接口方法中调用RealSubject中相应的方法执行;
Cliect客户端类——使用代理对象的类。
代理模式分为静态代理和动态代理,严格按照上述角色定义编写的代码属于静态代理,即在代码运行前ProxySubject代理类的class编译文件就已存在。Retrofit使用的是动态代理,是通过反射机制来动态生成方法接口的代理对象的。动态代理的实现是通过JDK提供的InvocationHandler接口,实现该接口重写其调用方法invoke。
复制代码
publicTcreate(finalClassservice){
Utils.validateServiceInterface(service);
if(validateEagerly){
eagerlyValidateMethods(service);
}
return(T)Proxy.newProxyInstance(service.getClassLoader(),newClass>[]{service},
newInvocationHandler(){
privatefinalPlatformplatform=Platform.get();
@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object...args)
throwsThrowable{
//IfthemethodisamethodfromObjectthendefertonormalinvocation.
if(method.getDeclaringClass()==Object.class){
returnmethod.invoke(this,args);
}
if(platform.isDefaultMethod(method)){
returnplatform.invokeDefaultMethod(method,service,proxy,args);
}
ServiceMethodserviceMethod=loadServiceMethod(method);
OkHttpCallokHttpCall=newOkHttpCall<>(serviceMethod,args);
returnserviceMethod.callAdapter.adapt(okHttpCall);
}
});
}
复制代码
Proxy.newProxyInstance返回接口的动态代理类,InvocationHandler的invoke方法处理method分为三种情况:1)Object的方法,直接返回Object的实现;2)判断是否Java8支持的DefaultMethod;3)创建OkHttpCall,通过ServiceMethod转换为接口的动态代理类。
使用Retrofit的客户端通过create方法获取自定义HTTP请求的动态代理类,是客户端代码中最重要的部分之一。这里有三个重要组件:
ServiceMethod
OKHttpCall
ServiceMethod.callAdapter
ServiceMethod用于处理ApiService上定义的注解,参数等,得到这个ServiceMethod之后,传给OkHttpCall,这个OkHttpCall就是对Okhttp的网络请求封装的一个类。
复制代码
ServiceMethodloadServiceMethod(Methodmethod){
ServiceMethodresult;
synchronized(serviceMethodCache){
result=serviceMethodCache.get(method);
if(result==null){
result=newServiceMethod.Builder(this,method).build();
serviceMethodCache.put(method,result);
}
}
returnresult;
}
复制代码
关于同步,这里有两点值得学习:
采用synchronized将锁加在serviceMethodCache上,而不是加到方法上。(直接synchronized加到方法可能会引起Dos,当然,你可以说客户端不用考虑这种问题);
serviceMethodCache是用于缓存HTTP请求方法的,初始方法采用LinkedHashMap而不是普通的HashMap。
privatefinalMapserviceMethodCache=newLinkedHashMap<>();
LinkedHashMap中有一个字段accessOrder,表示是否按照访问顺序进行重排序。默认为false,表示按插入时间顺序排序,如果设置为true,则进行重排序。最近最少访问的元素会被放到队尾,最先删除,而最常访问的元素,则放到队头,最后删除。
把ServiceMethod传给OkHttpCall实际上就是把网络接口所需要的URL,参数等条件传给了OkHttpCall,就可以进行网络请求了。ServiceMethod中如果没有配置CallAdapter,则使用默认的DefaultCallAdapterFactory,得到的结果是Call>。
回到正题,你可能已经发现,create方法采用的代理模式和正常的代理模式并不一样,正常的代理模式只是对真实对象的一层控制,这个真实对象是实现对应的接口的,而这里并没有真实的对象,它把方法调用最终全部转发到OKHttp了。
四、简单工厂模式
本文后面的部分将集中在简单工厂模式、工厂模式和抽象工厂模式,它们都属于创建型模式,其主要功能都是将对象的实例化部分抽取出来。简单工厂模式也称为静态工厂模式,包含三种角色:
Factory工厂角色——负责实现创建所有实例的内部逻辑;
Product抽象产品角色——创建的所有对象的父类,负责描述所有实例所共有的公共接口;
ConcreteProduct具体产品角色——继承自Product,负责具体产品的创建。
简单工厂模式是一种很常见、很简单的设计模式,以Platform类为例,其首先包含静态域PLATFORM,并通过静态返回供客户端调用。
privatestaticfinalPlatformPLATFORM=findPlatform();
staticPlatformget(){
returnPLATFORM;
}
findPlatform其实就是一个静态工厂方法,根据Class.forName是否抛出ClassNotFoundException来判断不同的平台。
复制代码
privatestaticPlatformfindPlatform(){
try{
Class.forName("android.os.Build");
if(Build.VERSION.SDK_INT!=0){
returnnewAndroid();
}
}catch(ClassNotFoundExceptionignored){
}
try{
Class.forName("java.util.Optional");
returnnewJava8();
}catch(ClassNotFoundExceptionignored){
}
try{
Class.forName("org.robovm.apple.foundation.NSObject");
returnnewIOS();
}catch(ClassNotFoundExceptionignored){
}
returnnewPlatform();
}
复制代码
而Android、Java8、IOS相当于ConcreteProduct的角色,继承自抽象产品类Platform。
Java8:
staticclassJava8extendsPlatform{}
Android:
staticclassAndroidextendsPlatform{}
IOS:
staticclassIOSextendsPlatform{}
五、工厂模式
上述简单工厂模式中的工厂方法类只有一个Factory,仍以Platform为例,如果在增加一种平台:WindowsPhone,那么就需要修改findPlatform方法,添加WindowsPhone类的创建。
这种修改模式不符合“开闭原则”,即对扩展开放,对修改封闭。本着可扩展的原则,抽象Factory类的公共部分为抽象类,然后不同的平台工厂继承自抽象的Factory。需要不用的平台就调用不同的工厂方法,这就是工厂模式。即对简单工厂中的工厂类进行抽象:
Factory抽象工厂类——负责工厂类的公共部分;
ConcreteFactory具体工厂类——继承自Factory,实现不同特性的工厂。
如果按照工厂模式,通过PlatformFactory类抽象工厂方法,那么大概会是这样:
publicabstractclassPlatformFactory{
abstractPlatformfindPlatform();
}
Android、Java8、IOS或者可能新增的WindowsPhone工厂继承自PlatformFactory。
复制代码
publicclassAndroidFactoryextendsPlatformFactory{
@Override
PlatformfindPlatform(){
try{
Class.forName("android.os.Build");
if(Build.VERSION.SDK_INT!=0){
returnnewAndroid();
}
}catch(ClassNotFoundExceptionignored){
}
returnnewPlatform();
}
}
复制代码
客户端需要不同的平台对象就调用不同的工厂,但客户端如果调用错误,比如在Android上调用了IOS的工厂,那么就会得到一个Platform的实例,这并不符合要求,这种写法增加了客户端的难度,同时,需要引入抽象层,增加多个具体工厂类,维护成本也更大。
所以,在使用工厂设计模式时,一定需要衡量利弊,在特定的场景选择最合适的设计模式。那么,Retrofit中使用工厂模式的经典例子又是什么呢?CallAdapter!
复制代码
publicinterfaceCallAdapter{
TyperesponseType();
Tadapt(Callcall);
abstractclassFactory{
publicabstractCallAdapter>get(TypereturnType,Annotation[]annotations,Retrofitretrofit);
protectedstaticTypegetParameterUpperBound(intindex,ParameterizedTypetype){
returnUtils.getParameterUwww.wang027.compperBound(index,type);
}
protectedstaticClass>getRawType(Typetype){
returnUtils.getRawType(type);
}
}
}
复制代码
CallAdapter是什么呢?见名知义,对Call进行适配,这里涉及到适配器模式,下节会着重说明。这里关注CallAdapter.Factory,CallAdapter.Factory对应上述角色中的Factory抽象工厂类,包含两个静态工具方法getParameterUpperBound、getRawType和抽象方法get。
get方法返回不同类型的CallAdapter,RxJavaCallAdapterFactory返回CallAdapter>,DefaultCallAdapterFactory返回CallAdapter>。
复制代码
publicfinalclassRxJavaCallAdapterFactoryextendsCallAdapter.Factory{
//省略代码
@Override
publicCallAdapter>get(TypereturnType,Annotation[]annotations,Retrofitretrofit){
//省略代码
CallAdapter>callAdapter=getCallAdapter(returnType,scheduler);
//省略代码
returncallAdapter;
}
privateCallAdapter>getCallAdapter(TypereturnType,Schedulerscheduler){
//省略代码
}
}
复制代码
如果需要增加新的CallAdapter,继承自CallAdapter.Factory,覆盖get方法即可。符合面向对象软件设计的“开闭原则”。
六、抽象工厂模式
在配置Retrofit时,除了上述提到的CallAdapter,还需要addConverterFactory。Retrofit调用Okhttp时,将请求内容由T转换为okhttp3.RequestBody,将返回内容由okhttp3.ResponseBody转换为T,Converter是就是负责转换的类。Retrofit官方文档中给出了多种不同实现的转换器类,如下:
Gson:com.squareup.retrofit2:converter-gson
Jackson:com.squareup.retrofit2:converter-jackson
Moshi:com.squareup.retrofit2:converter-moshi
Protobuf:com.squareup.retrofit2:converter-protobuf
Wire:com.squareup.retrofit2:converter-wire
SimpleXML:com.squareup.retrofit2:converter-simplexml
Scalars(primitives,boxed,andString):com.squareup.retrofit2:converter-scalars
其中包含JSON转换的Gson、Jackson,负责PB解析的Protobuf,负责XML解析的SimpleXML,这些类具有相同点,均继承自Converter.Factory。
复制代码
publicinterfaceConverter{
Tconvert(Fvalue)throwsIOException;
abstractclassFactory{
publicConverterresponseBodyConverter(Typetype,Annotation[]annotations,
Retrofitretrofit){
returnnull;
}
publicConverter,RequestBody>requestBodyConverter(Typetype,
Annotation[]parameterAnnotations,Annotation[]methodAnnotations,Retrofitretrofit){
returnnull;
}
publicConverter,String>stringConverter(Typetype,Annotation[]annotations,Retrofitretrofit){
returnnull;
}
}
}
复制代码
这里注意下Converter.Factory与CallAdapter.Factory的区别,CallAdapter.Factory只有一个抽象的get方法返回CallAdapter>,Converter.Factory有三个方法,分别返回Converter、Converter,RequestBody>和Converter,String>。
相比于工厂模式,具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品,工厂方法也具有唯一性,一般情况下,一个具体工厂中只有一个工厂方法或者一组重载的工厂方法。但是有时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象,例如:上述Converter.Factory需要同时提供请求内容和返回内容的转换类,这时,就需要考虑抽象工厂模式。抽象工厂模式同样包含四种角色:
AbstractFactory:抽象工厂
ConcreteFactory:具体工厂
AbstractProduct:抽象产品
Product:具体产品
标准抽象工厂模式的UML图如下:(图片来自互联网)
AbatractFactory
这里以GsonConverterFactory为例进行说明。GsonConverterFactory对应ConcreteFactory具体工厂,表示Gson转换类的工厂,GsonConverterFactory继承自AbstractFactory抽象工厂——Converter.Factory,重写了requestBodyConverter方法和responseBodyConverter方法,相当于上图中的createProductA和createProductB。
复制代码
publicfinalclassGsonConverterFactoryextendsConverter.Factory{
//省略代码
@Override
publicConverterresponseBodyConverter(Typetype,Annotation[]annotations,
Retrofitretrofit){
TypeAdapter>adapter=gson.getAdapter(TypeToken.get(type));
returnnewGsonResponseBodyConverter<>(gson,adapter);
}
@Override
publicConverter,RequestBody>requestBodyConverter(Typetype,
Annotation[]parameterAnnotations,Annotation[]methodAnnotations,Retrofitretrofit){
TypeAdapter>adapter=gson.getwww.baiyuewang.netAdapter(TypeToken.get(type));
returnnewGsonRequestBodyConverter<>(gson,adapter);
}
}
复制代码
这里GsonRequestBodyConverter对应ProductA,GsonResponseBodyConverter对应ProductB。
finalclassGsonResponseBodyConverterimplementsConverter{
//省略代码
}
finalclassGsonRequestBodyConverterimplementsConverter{
//省略代码
}
Converter对应抽象产品AbstractProductA,Converter对应抽象产品AbstractProductB。
第二部分
一、适配器模式
在上篇说明CallAdapter.Factory使用工厂模式时,提到CallAdapter本身采用了适配器模式。适配器模式将一个接口转换成客户端希望的另一个接口,使接口本不兼容的类可以一起工作。
Call接口是Retrofit内置的发送请求给服务器并且返回响应体的调用接口,包括同步、异步请求,查询、取消、复制等功能。
复制代码
publicinterfaceCallextendsCloneable{
//同步执行请求
Responseexecute()throwsIOException;
//异步执行请求
voidenqueue(Callbackcallback);
//省略代码
//取消请求
voidcancel();
//复制请求
Callclone();
}
复制代码
而客户端可能希望更适合业务逻辑的接口回调,比如响应式的接口回调。那么,就需要对Call进行转换,CallAdapter就上场了。CallAdapter包含两个方法:
复制代码
publicinterfaceCallAdapter{
//返回请求后,转换的参数Type类型
TyperesponseType();
//接口适配
Tadapt(Callcall);
}
复制代码
如果客户端没有配置CallAdapter,Retrofit会采用默认的实现DefaultCallAdapterFactory直接返回Call对象,而如果配置了RxJava的RxJavaCallAdapterFactory实现,就会将Call转换为Observable,供客户端调用。
复制代码
staticfinalclassSimpleCallAdapterimplementsCallAdapter>{
//省略代码
@Override
publicObservableadapt(Callcall){
Observableobservable=Observable.create(newCallOnSubscribe<>(call))
.lift(OperatorMapResponseToBodyOrError.instance());
if(scheduler!=null){
returnobservable.subscribeOn(scheduler);
}
returnobservable;
}
}
复制代码
总结下,适配器模式包含四种角色:
Target:目标抽象类
Adapter:适配器类
Adaptee:适配者类
Client:客户端类
CallAdapter对应Target,其adapt方法返回客户端类Client需要的对象;RxJavaCallAdapterFactory的get方法返回SimpleCallAdapter对象(或ResultCallAdapter对象)实现了CallAdapter>,对应Adapter;Call对应Adaptee适配者类,包含需要被适配的方法。
另外,适配器模式有对象适配器和类适配器两种实现。类适配器中的Adapter需要继承自Adaptee,对象适配则是采用复合的方式,Adapter持有Adaptee的引用。类适配器模式会使Adaptee的方法暴露给Adapter,根据“复合优先于继承”的思想,推荐使用对象适配器模式。
值得说明的是,这里SimpleCallAdapter并没有通过域的方式持有Call,而是直接在CallAdapter的get方法中将Call以入参形式传入。虽然并不是教科书式的对象适配器模式,但使用却更加灵活、方便。
二、策略模式
完成一项任务,往往可以有多种不同的方式,每一种方式称为一个策略,我们可以根据环境或者条件的不同选择不同的策略来完成该项任务。针对这种情况,一种常规的做法是将多个策略写在一个类中,通过if…else或者switch等条件判断语句来选择具体的算法。这种方式实现简单、快捷,但维护成本很高,当添加新的策略时,需要修改源代码,这违背了开闭原则和单一原则。仍以CallAdapter为例,不同的CallAdapter代表着不同的策略,当我们调用这些不同的适配器的方法时,就能得到不同的结果,这就是策略模式。策略模式包含三种角色:
Context上下文环境——区别于Android的Context,这里代表操作策略的上下文;
Stragety抽象策略——即不同策略需要实现的方法;
ConcreteStragety策略实现——实现Stragety抽象策略。
在Retrofit中,配置Retrofit.Builder时addCallAdapterFactory,配置的类就对应Context;不同的CallAdapter都需要提供adapt方法,CallAdapter就对应Stragety抽象策略。RxJavaCallAdapterFactory的get方法返回SimpleCallAdapter对象(或ResultCallAdapter对象)就对应具体的策略实现。
这里可能会跟上篇中的工厂模式搞混,在说明工厂模式时,主要是强调的是:
publicabstractCallAdapter>get(TypereturnType,Annotation[]annotations,Retrofitretrofit);
通过get方法返回不同的CallAdapter对象;策略模式强调的是这些不同CallAdapter对象的adapt方法的具体实现。
Tadapt(Callcall);
总结下:工厂模式强调的是生产不同的对象,策略模式强调的是这些不同对象的策略方法的具体实现,是在创建对象之后。
三、观察者模式
建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。在此,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展,这就是观察者模式的模式动机。
举个栗子:在Android编程中,常见的一种情况是界面上某个控件的状态对其它控件有约束关系,比如,需要根据某个EditText的输入值决定某个按钮是否可以点击,就需要此EditText是可观测的对象,而按钮是EditText的观测者,当EditText状态发生改变时,按钮进行相应的操作。
观察者模式包含四种角色:
Subject抽象主题——也就是被观察对象,Observable是JDK中内置的类(java.util.Observable),当需要定义被观察对象时,继承自Observable即可;
ConcreteSubject具体主题——具体被观察者,可以继承Observable实现,需要通知观察者时,调用notifyObservers;
Observer抽象观察者——Observer也是JDK内置的,定义了update方法;
ConcreteObserver具体观察者——实现Observer接口定义的update方法,以便在状态发生变化时更新自己。
publicinterfaceObserver{
voidupdate(Observableobservable,Objectdata);
}
复制代码
publicclassObservable{
Listobservers=newArrayList();
//省略代码
publicvoidnotifyObservers(Objectdata){
intsize=0;
Observer[]arrays=null;
synchronized(this){
if(hasChanged()){
clearChanged();
size=observers.size();
arrays=newObserver[size];
observers.toArray(arrays);
}
}
if(arrays!=null){
for(Observerobserver:arrays){
observer.update(this,data);
}
}
}
}
复制代码
所有与网络请求相关的库一定会支持请求的异步发送,通过在库内部维护一个队列,将请求添加到该队列,同时注册一个回调接口,以便执行引擎完成该请求后,将请求结果进行回调。Retrofit也不例外,Retrofit的网络请求执行引擎是OkHttp,请求类是OkHttpCall,其实现了Call接口,enqueue方法如下,入参为Callback对象。
voidenqueue(Callbackcallback);
在OkHttpCall的enqueue实现方法中,通过在okhttp3.Callback()的回调方法中调用上述入参Callback对象的方法,实现通知观察者。
复制代码
@Override
publicvoidenqueue(finalCallbackcallback){
//省略代码
call.enqueue(newokhttp3.Callback(){
@Override
publicvoidonResponse(okhttp3.Callcall,okhttp3.ResponserawResponse)
throwsIOException{
Responseresponse;
try{
response=parseResponse(rawResponse);
}catch(Throwablee){
callFailure(e);
return;
}
callSuccess(response);
}
@Override
publicvoidonFailure(okhttp3.Callcall,IOExceptione){
try{
callback.onFailure(OkHttpCall.this,e);
}catch(Throwablet){
t.printStackTrace();
}
}
privatevoidcallSuccess(Responseresponse){
try{
callback.onResponse(OkHttpCall.this,response);
}catch(Throwablet){
t.printStackTrace();
}
}
复制代码
总结下:Call接口对应Subject,定义被观察者的特性,包含enqueue等;OkHttpCall对应ConcreteSubject具体被观察者,Callback对应Observer抽象观察者,Callback的实现类对应ConcreteObserver具体观察者。
四、单例模式
单例模式可能是所有设计模式教程的第一个讲到的模式,也是应用最广泛的模式之一。Retrofit中也使用了大量的单例模式,比如BuiltInConverters的responseBodyConverter、requestBodyConverter等,并且使用了饿汉式的单例模式。由于这种单例模式应用最广,也是大家都清楚的,本节将扩展下单例模式的其它实现方式:
懒汉式单例模式:
复制代码
publicclassSingleton{
privatestaticSingletoninstance;
privateSingleton(){
}
publicstaticsynchronizedSingletongetInstance(){
if(instance==null){
instance=newSingleton();
}
returninstance;
}
}
复制代码
懒汉单例模式的优点是单例只要有在使用是才被实例化,缺点是美的调用getInstance都进行同步,造成不必要的同步开销。
DCL(DoubleCheckLock):
复制代码
publicclassSingleton{
privatestaticSingletoninstance;
privateSingleton(){
}
publicstaticSingletongetInstance(){
if(instance==null){
synchronized(Singleton.class){
if(instance==null){
instance=newSingleton();
}
}
}
returninstance;
}
}
复制代码
DCL是对懒汉单例模式的升级,getInstance方法对instance进行了两次判空,第一层判断是为了避免不必要的同步,第二层判断是为了在null时创建实例,这里涉及到对象实例化过程的原子问题。在Java中,创建对象并非原子操作,而是包含分配内存、初始化成员字段、引用指向等一连串操作,而多线程环境下,由于指令重排序的存在,初始化指令和引用指令可能是颠倒,那么可能当线程执行第一个判断不为null返回的对象,却是未经初始化的(别的对象创建Singleton时,初始化指令和引用指令颠倒了)。
静态内部类:
复制代码
publicclassSingleton{
privateSingleton(){
}
publicstaticSingletongetInstance(){
returnSingletonHolder.instance;
}
privatestaticclassSingletonHolder{
privatestaticfinalSingletoninstance=newSingleton();
}
}
复制代码
上述DCL也是可能失效的,具体可参考《有关“双重检查锁定失效”的说明》。采用静态内部类,加载Singleton类时并不会初始化instance,同时也能保证线程安全,单例对象的唯一性。
枚举单例:
publicenumSingleton{
INSTANCE;
}
枚举实例的创建默认是线程安全的,并且在任何情况下都只有一个实例。上述单例模式存在反序列化会重新创建对象的情况,而枚举不存在这个问题。但Android编程中,因为性能问题,不推荐使用枚举,所以,这种比较怪异的方式并不推荐。
使用容器实现单例模式:
复制代码
publicclassSingleton{
privatestaticMapobjectMap=newHashMap<>();
publicstaticvoidaddObject(Stringkey,Objectinstance){
if(!objectMap.containsKey(key)){
objectMap.put(key,instance);
}
}
publicstaticObjectgetObject(Stringkey){
returnobjectMap.get(key);
}
}
复制代码
严格的讲,这并不是标准的单例模式,但确实实现了单例的效果。
单例的核心原理是将构造函数私有化,通过静态方法获取唯一实例。而怎么获取唯一实例?在Java中可能存在线程安全、反序列化等问题,因此衍生出上述这几个版本。在实际使用时需要根据并发环境、JDK版本以及资源消耗等因素综合考虑。
五、原型模式
原型模式是一种创建型模式,主要用于对象复制。使用原型模式创建对象比直接new一个对象在性能上要好的多,因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流。使用原型模式的另一个好处是简化对象的创建,使得创建对象就像在编辑文档时的复制粘贴。基于以上优点,在需要重复地创建相似对象时可以考虑使用原型模式。比如需要在一个循环体内创建对象,假如对象创建过程比较复杂或者循环次数很多的话,使用原型模式不但可以简化创建过程,而且可以使系统的整体性能提高很多。
原型模式有三种角色:
Client客户端;
Prototype原型——一般表现为抽象类或者接口,比如JDK中的Cloneable接口;
ConcretePrototype具体原型类——实现了Prototype原型。
OkHttpCall实现了Call接口,Call接口继承自Cloneable,OkHttpCall的clone方法实现如下:
@Override
publicOkHttpCallclone(){
returnnewOkHttpCall<>(serviceMethod,args);
}
clone的实现就是重新new了一个一样的对象,用于其他地方重用相同的Call,在ExecutorCallbackCall中有用到:
复制代码
staticfinalclassExecutorCallbackCallimplementsCall{
//省略代码
@SuppressWarnings("CloneDoesntCallSuperClone")//Performingdeepclone.
@Override
publicCallclone(){
returnnewExecutorCallbackCall<>(callbackExecutor,delegate.clone());
}
}
复制代码
使用原型模式复制对象需要主要深拷贝与浅拷贝的问题。Object类的clone方法只会拷贝对象中的基本的数据类型,对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。
六、享元模式
享元模式是对象池的一种实现,运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式(Flyweight),它是一种对象结构型模式。
享元模式包含三种角色:
Flyweight享元基类或接口;
ConcreteFlyweight具体的享元对象;
FlyweightFactory享元工厂——负责管理享元对象池和创建享元对象。
Retrofit中create方法创建ServiceMethod是通过loadServiceMethod方法实现。loadServiceMethod方法就实现了享元模式。
复制代码
privatefinalMapserviceMethodCache=newLinkedHashMap<>();
ServiceMethodloadServiceMethod(Methodmethod){
ServiceMethodresult;
synchronized(serviceMethodCache){
result=serviceMethodCache.get(method);
if(result==null){
result=newServiceMethod.Builder(this,method).build();
serviceMethodCache.put(method,result);
}
}
returnresult;
}
|
|