前几天爱哥在群里跟群里的各位大大讲过一个很简单但是很实用的架构小例子,就是利用接口简单地将我们的实现分离出去,以应对项目中不同的变化,这个架构的例子非常简单,只要你了解接口、一眼就能看懂,不过在此之前大家要明白一点的是很多情况下我们的架构都是依赖于某个具体的业务模型,如果这个业务模型变更了那么我们架构也有可能就不再适用,这里很多朋友可能会打爱哥脸,BullShit!简直鬼扯,架构不应该就是为了解决多变的业务逻辑才存在的吗!!是的没错,架构是应该为了解决多变的业务逻辑而存在,但是,如果我们的整个业务模型都变了呢?打个不恰当的比方,人可以穿不同的衣服来表现自己不同的个性,这是可以的,衣服就像那多变的业务逻辑,而人是应用于这套业务逻辑的模型,如果突然人变成了狗,你那些漂亮衣服还有个JB毛用啊!!!废话少说,我们先来看看这个例子,这个例子呢是一个网络请求接口的封装例子,再说这个例子前我先跟大家说说服务器返回的JSON结构,JSON的结构是下面这个样子的:
也就是说我们的基本JSON结构里有四个字段:code、data、msg、time,其中code、msg和time是每个响应JSON的基本字段,而data字段里的数据就是我们根据不同接口请求所返回的数据,这里code表示响应码,当且仅当code==200时data里才会有数据,而msg呢则表示一些额外的附加信息,如果code==200,msg不会有任何信息,否则msg会填充相应的信息说明为什么请求不成功,最后的time则表示请求响应成功的时间戳。OK,有了这么一个JSON结构,我们就可以在此结构上封装我们的网路请求框架,首先,我们根据JSON的结构新建一个响应的实体类:
* 网络请求数据响应包装类这个类很简单,基本与我们的JSON结构对应,在这个例子中,爱哥只演示两个接口的封装:登录与版本更新,其他的大家可以自行尝试练手玩,对于登录和版本更新,我们也应该根据服务器返回的JSON结构建立不同的数据实体类,假设我的登录JSON是酱紫的:
*
* @author AigeStudio{@link http://aigestudio.com/?p=100}
* @since 2016-08-15
*/
public final class Response<T> {
public T mData;// 数据实体
public String mResponseMsg;// 响应消息文本 在mResponseCode不等于200的情况下该字段才会有数据
public long mResponseTimeStamp;// 响应请求的时间戳
public int mResponseCode;// 具体的相应码 200表示请求并响应成功 该值由服务器规定
}
那么我们对应的数据实体类就应该是酱紫的:
/**
* 登录信息数据实体类
*
* @author AigeStudio{@link http://aigestudio.com/?p=100}
* @since 2016-08-15
*/
public class LoginInfo {
public String mUser;// 用户名
public String mPassword;// 用户密码
public String mName;// 用户昵称
public String mAvatarUrl;// 用户头像地址
public String mSex;// 用户性别
public String mBirthday;// 用户生日
public long mRegisterTimeStamp;// 用户注册时的时间戳
public long mLastLoginTimeStamp;// 用户上一次登陆的时间戳
public int mAge;// 用户年龄
}
假设我的登录JSON是酱紫的:
那么我们对应的数据实体类就应该是酱紫的:
/**
* 应用更新信息数据实体类
*
* @author AigeStudio{@link http://aigestudio.com/?p=100}
* @since 2016-08-15
*/
public class UpgradeInfo {
public String mVersionName;// 版本名
public String mDescription;// 版本描述
public String mDownloadUrl;// 安装包下载地址
public int mVersionCode;// 版本号
public boolean isForce;// 是否强制更新
}
有童鞋可能会问,诶?爱哥这两个登录的和版本更新的数据实体木有code、msg、time什么的啊,这里要注意的是这个两个数据实体对应的是JSON结构中的data部分,而至于通用的code、msg和time,我们早就在上面的Response类里声明了,大家注意上面的Response类爱哥用了一个泛型去表示data的类型,为的就是根据传入的具体的数据实体来封装对应的响应实体对象,同时这里爱哥还用了一个final修饰符修饰Response类,也就是说这个Response类不能被继承,从它出生那刻起它就是这样了,除非我们的JSON基础结构改变,否则它就不会变。可能又有好奇的童鞋会问爱哥这里应该也可以直接用继承,让登录和版本更新的数据实体类继承Response类就好了啊,没错,是可以这么干,但是这里使用泛型可以使我们的类结构与JSON结构更符合不是麽?
现在我们的数据实体类定义完了,是时候来看看业务逻辑了,本文的标题是“巧用接口解耦分离实现”,这里我们的网络请求接口方法只有两个:登录和版本更新,我们先定义一个接口来封装这两个方法的声明:
/**一个登陆方法和一个版本更新方法,对登陆方法而言,我们需要传入两个必要参数:用户名和密码,而对于版本更新而言,我们需要传入一个当前版本的版本号,用于和服务器的最新版本做对比,这些呢都是我们业务模型所决定的,跟代码逻辑无关,但是我们的架构得跟着这些业务模型变化。大家一定注意到上面的两个方法中我们都加了一个CallBack类型的参数,我们先来看看这是啥玩意:
* 网络请求方法接口
*
* @author AigeStudio{@link http://aigestudio.com/?p=100}
* @since 2016-08-15
*/
interface IApi {
/**
* 用户登录
*
* @param user 用户账户
* @param password 用户密码
* @param c 数据请求完成后回调接口
*/
void login(String user, String password, CallBack<LoginInfo> c);
/**
* 应用更新
*
* @param code 版本号
* @param c 数据请求完成后回调接口
*/
void upgrade(String code, CallBack<UpgradeInfo> c);
}
/**
* 网络请求完成后的回调接口
*
* @param <T> 请求完成后返回的数据实体类
* @author AigeStudio{@link http://aigestudio.com/?p=100}
* @since 2016-08-15
*/
public interface CallBack<T> {
/**
* 请求完成后回调该方法
*
* @param isSuccess 请求是否成功
* @param response 如果请求成功该参数不为空否则为空
* @param error 如果请求成功该参数为空否则会将失败原因传入
*/
void onFinish(boolean isSuccess, Response<T> response, String error);
}
CallBack其实就是一个回调接口,大家知道我们网络请求是耗时的,我们不能在主线程里处理这些耗时操作,因此我们需要在子线程里去请求,那么我们就需要在请求完成后将结果返回给调用者,而CallBack则处理这个逻辑。
现在我们声明了接口,接下来就该实现具体的逻辑了,现在问题来了,对于网络请求来说,我们又很多成熟的框架,最基本的我们可以使用HttpURLConnection自己原汁原味地实现一个,也可以用封装的OkHttp去实现,也可以使用当下较为流行的RESTFUL框架,不管你使用哪种方式,都该是遵循我们的业务模型的,不管底层逻辑如何实现,都不应该影响我们上层的结构,这里的体现就是我们上面定义的接口和具体的实现类。这里我们有三种实现:HttpURLConnection、OkHttp和RESTFUL,我们对应不同的实现类:
/**
* 网络请求方法常规实现
*
* @author AigeStudio{@link http://aigestudio.com/?p=100}
* @since 2016-08-15
*/
class ApiNor implements IApi {
@Override
public void login(String user, String password, CallBack<LoginInfo> c) {
}
@Override
public void upgrade(String code, CallBack<UpgradeInfo> c) {
}
}
/** * 网络请求方法OkHttp实现 * * @author AigeStudio{@link http://aigestudio.com/?p=100} * @since 2016-08-15 */class ApiOkHttp implements IApi { @Override public void login(String user, String password, CallBack<LoginInfo> c) { } @Override public void upgrade(String code, CallBack<UpgradeInfo> c) { }}
/**
* 网络请求方法RESTFUL实现
*
* @author AigeStudio{@link http://aigestudio.com/?p=100}
* @since 2016-08-15
*/
class ApiRest implements IApi{
@Override
public void login(String user, String password, CallBack<LoginInfo> c) {
}
@Override
public void upgrade(String code, CallBack<UpgradeInfo> c) {
}
}
这里很多童鞋会问爱哥为什么一个网络请求封装要三种不同的实现呢?仅仅是为了演示吗?NO NO NO,这绝不是演习,事实上我们在实际开发中很有可能遇到这样的情况,如上所说网络请求有很多封装好的框架,但是这些框架即便由很牛逼的大神所写也难免会出现一些小BUG,如何项目紧急而你又无法去解决这些BUG的时候,使用这样的接口隔离实现能让你快速在不同的网络请求框架之间切换且不影响上层结构。说了这么多,我们来看看怎么用,这里爱哥使用一个功能类来封装这三种不同的实现:
/**
* 网络请求方法单例封装类
*
* @author AigeStudio{@link http://aigestudio.com/?p=100}
* @since 2016-08-15
*/
public final class Api implements IApi {
private volatile static Api sInstance;
private IApi mApi;
private Api() {
mApi = new ApiNor();
}
public static Api getInstance() {
if (null == sInstance)
synchronized (Api.class) {
sInstance = new Api();
}
return sInstance;
}
@Override
public void login(String user, String password, CallBack<LoginInfo> c) {
mApi.login(user, password, c);
}
@Override
public void upgrade(String code, CallBack<UpgradeInfo> c) {
mApi.upgrade(code, c);
}
}
这个功能类很简单,大家可以看到它也是实现了IApi接口,本质而言也是个IApi的实现类,不同的是其不实现任何具体的逻辑而只是简单地调用上面那三个实现类对应的逻辑而已,这里我们构造的是ApiNor对象:
mApi = new ApiNor();
也就是说Api里所有的实现方法实质上都是调用的ApiNor的逻辑实现。最后我们再看看如何在上层中使用:
/**
* 客户类
*
* @author AigeStudio{@link http://aigestudio.com/?p=100}
* @since 2016-08-15
*/
public class Main {
public static void main(String[] args) {
Api.getInstance().login("AigeStudio", "123456789", new CallBack<LoginInfo>() {
@Override
public void onFinish(boolean isSuccess, Response<LoginInfo> response, String error) {
if (isSuccess) {
int code = response.mResponseCode;
if (code == 200) {
String user = response.mData.mUser;
String password = response.mData.mPassword;
String name = response.mData.mName;
String avatarUrl = response.mData.mAvatarUrl;
String sex = response.mData.mSex;
String birthday = response.mData.mBirthday;
long registerTimeStamp = response.mData.mRegisterTimeStamp;
long lastLoginTimeStamp = response.mData.mLastLoginTimeStamp;
int age = response.mData.mAge;
} else {
System.out.print(response.mResponseMsg);
}
} else {
System.out.print(error);
}
}
});
}
}
我们在main方法中简单地调用login方法模拟登录,这个网络请求框架我们就算完结了,假使有一天,ApiNor里的逻辑出现了BUG而我们又没时间改,那么我们可以简单地切到OkHttp的实现中:
mApi = new ApiOkHttp();
而这个改动,你不需要去更改任何上层的代码,也就是说我们Main类中的代码不需要任何改动,你只需要做相应的底层实现即可,这就是一个很简单的架构小例子,利用接口分离实现。除此之外,我们还可以在Api类中根据不同的接口方法调用不同的实现,比如对于登录我想用RESTFUL实现而对于版本更新我想用OkHttp实现等,这些底层的改动都不会影响上层的结构。
你可以把这种结构应用于很多很多地方,相信我熟悉了你会爱上它的,简单但是非常实用,特别是在引入第三方框架的时候,对第三方框架做一层封装能让你的项目结构更灵活。
原文链接:http://aigestudio.com/?p=100