巧用接口解耦分离实现
架构对于很多初学者来讲往往都是唯恐不及的,总感觉架构对自己而言太过遥远,实际上架构对大家来说并不陌生,甚至你每天都在与之打交道,只是你习以为常没有留意而已。生活中处处是架构设计的影子,只不过在日常生活中我们不称之为架构罢了。记得在一年多以前爱哥在公司楼下的食堂发现一个有趣的现象,楼下食堂打饭是这样的,你得先去食堂售票处那够买饭票,然后拿着这张饭票排队点餐,点餐的过程是这样的,先会有一个大叔问你吃什么,有A、B、C、D餐,你告诉大叔要B餐,然后大叔吼一声“B”,干净利落!站在大叔身后的阿姨就以迅雷不及掩耳之势打了一份B餐放到前面,最后打水果和配汤的小哥就顺势放上一份水果和配汤到餐盘里,你就可以端走你的套餐去啵叽啵叽了,整个过程一气呵成不过十来秒时间。这里一个简单的食堂打饭过程,涉及到了三个人员的配置:猥琐的叫卖大叔、抠鼻屎往菜里扔的阿姨、不住对着水果和配汤打喷嚏的小哥……那么为什么要分三个人来做呢?这估计傻子都看得出来,社会分工效率更高是吧,把这种分工思想应用到我们代码里,就是一种简单的分层架构体现,如果我们再往细里想想,你就发现上面食堂打饭的过程大有学问,首先我们来看看猥琐大叔,猥琐大叔是负责跟我们直接接触的,我们可以把他看作是我们架构中的界面层,界面层只管与用户交互的逻辑,这里猥琐大叔也确实只是在问我们要吃什么,而龌龊阿姨呢,则不会理会到底是谁要的哪个套餐,她只接收来自猥琐大叔的指令,猥琐大叔告诉她接下来要打哪个餐,龌龊阿姨并不和点餐的人有任何直接的接触,因此这里我们可以将龌龊阿姨看作是业务逻辑处理的一部分,最后我们来看看孤独的小哥,小哥的作用很单纯,他不接受任何人的指令,他的唯一工作就是重复不断地把水果和配汤放到每一个餐盘里,附送一个包含各种病菌的喷嚏……这里小哥虽然孤独,但是他也算是在处理自己的业务逻辑,也是整个点餐过程中不可或缺的一部分,因此我们也可以将小哥看作是业务处理层的一部分。如果大家能看到这一步,那么恭喜你,至少你已经有一丝架构逻辑的思想了,不过这个点餐过程我们还可以继续往深的地方看。算了,还是不看了……有点跑题了,爱哥大费周章地举了这个例子其实就想说明架构其实离我们真的很近,只要平日里大家多学会思考,多想想,就会发现我们身边到处都是架构思想的影子。
前几天爱哥在群里跟群里的各位大大讲过一个很简单但是很实用的架构小例子,就是利用接口简单地将我们的实现分离出去,以应对项目中不同的变化,这个架构的例子非常简单,只要你了解接口、一眼就能看懂,不过在此之前大家要明白一点的是很多情况下我们的架构都是依赖于某个具体的业务模型,如果这个业务模型变更了那么我们架构也有可能就不再适用,这里很多朋友可能会打爱哥脸,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的结构新建一个响应的实体类:
* 网络请求数据响应包装类** @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表示请求并响应成功 该值由服务器规定
}
这个类很简单,基本与我们的JSON结构对应,在这个例子中,爱哥只演示两个接口的封装:登录与版本更新,其他的大家可以自行尝试练手玩,对于登录和版本更新,我们也应该根据服务器返回的JSON结构建立不同的数据实体类,假设我的登录JSON是酱紫的:
/*** 登录信息数据实体类** @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结构更符合不是麽?
现在我们的数据实体类定义完了,是时候来看看业务逻辑了,本文的标题是“巧用接口解耦分离实现”,这里我们的网络请求接口方法只有两个:登录和版本更新,我们先定义一个接口来封装这两个方法的声明:
/*** 网络请求方法接口** @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);
}
一个登陆方法和一个版本更新方法,对登陆方法而言,我们需要传入两个必要参数:用户名和密码,而对于版本更新而言,我们需要传入一个当前版本的版本号,用于和服务器的最新版本做对比,这些呢都是我们业务模型所决定的,跟代码逻辑无关,但是我们的架构得跟着这些业务模型变化。大家一定注意到上面的两个方法中我们都加了一个CallBack类型的参数,我们先来看看这是啥玩意:
/*** 网络请求完成后的回调接口** @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 {@Overridepublic void login(String user, String password, CallBack<LoginInfo> c) {}@Overridepublic void upgrade(String code, CallBack<UpgradeInfo> c) {}
}
/*** 网络请求方法OkHttp实现** @author AigeStudio{@link http://aigestudio.com/?p=100}* @since 2016-08-15*/
class ApiOkHttp implements IApi {@Overridepublic void login(String user, String password, CallBack<LoginInfo> c) {}@Overridepublic void upgrade(String code, CallBack<UpgradeInfo> c) {}
}
/*** 网络请求方法RESTFUL实现** @author AigeStudio{@link http://aigestudio.com/?p=100}* @since 2016-08-15*/
class ApiRest implements IApi{@Overridepublic void login(String user, String password, CallBack<LoginInfo> c) {}@Overridepublic 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;}@Overridepublic void login(String user, String password, CallBack<LoginInfo> c) {mApi.login(user, password, c);}@Overridepublic 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>() {@Overridepublic 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
巧用接口解耦分离实现相关推荐
- 怎么实现接口解耦_将接口与实现解耦-使用分离的接口
怎么实现接口解耦 If you've ever faced the same dilemma, surely you'll grasp the sense of the following descr ...
- N4 接口解耦的可行性试验
目录 文章目录 目录 前言 N4 接口开放的背景 N4 接口开放技术验证与分析 验证环境 验证内容 PFCP Node 管理 PFCP 会话管理 计费 切换(End Marker 构造) 寻呼 验证结 ...
- java 模块分离部署_GitHub - yangjiu/Mis: 模块接口服务,如何在一个模块内维护其对外暴露的接口(包括打包发布),而不是把接口和接口实现分离到两个不同的模块?...
MIS 模块接口服务(Module Interface Service) MIS主要解决的问题是如何在一个模块内维护其对外暴露的接口(包括打包发布),而不是把接口和接口实现分离到两个不同的模块. Us ...
- 面向垂直行业的N4接口解耦技术
[摘 要]为了满足5G网络服务垂直行业场景下对边缘UPF轻量化.低成本和灵活部署的需求,需要推动支持N4接口解耦和轻量化UPF.首先对UPF进行了功能简化和分级,并提出分档容量和性能要求,然后对N4 ...
- swift 组件化_打造完备的iOS组件化方案:如何面向接口进行模块解耦?
作者 | 黑超熊猫zuik,一个修行中的 iOS 开发,喜欢搞点别人没搞过的东西,钻研过逆向工程.VIPER 架构和组件化. 关于组件化的探讨已经有不少了,在之前的文章 iOS VIPER架构实践(三 ...
- 互联网分层架构,为啥要前后端分离?
作者:58神剑,来源:架构师之路 一,典型后端架构 通用业务服务化之后,系统的典型后端结构如上: web-server通过RPC接口,从通用业务服务获取数据 biz-service通过RPC接口,从多 ...
- 解耦与人类行为 (完整)
卷首语 一个有OO设计经验的人都知道解耦给系统带来的好处:灵活性.扩展性--.其实,解耦早在OO出现之前很久就已经出现了. 萌芽 N年之前 雌雄同体 播种和生育都由同一个个体完成 几乎所有的古代传说和 ...
- spring整合dubbo实现简单分布式接口调用
随着微服务,分布式的概念越来越火,越来越多的互联网公司开始尝试使用分布式进行项目开发,分布式开发的好处毋庸置疑,分工明确,团队协作高效,安全,解耦分离等,其中以springcloud为代表的新分布式微 ...
- Java编程思想第四版读书笔记——第九章 接口
这章介绍了适配器设计模式和策略设计模式. 第九章 接口 接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法. 1.抽象类和抽象方法 public abstract void f(); 创 ...
最新文章
- git - 搭建最简单的git server
- BZOJ3738 : [Ontak2013]Kapitał
- 原始套接字抓取所有以太网数据包与分析
- html5如何绘制饼图,如何在HTML5中创建“饼图”?
- 编程让鼠标一直动_华硕、罗技、海盗船无线鼠标选哪个?
- PyPA Creating Documentation
- Nmap小技巧——探测大网络空间、局域网中的存活主机
- mysql3819错误,微软 Office 3819.20006 预览版发布:修复 Excel 导出 PDF 错误等问题
- 硬盘格式化数据恢复的软件推荐
- PHP通过PHPMailer类库实现QQ邮箱发送方法
- 国内主流云厂商下一代云主机最大可售卖384核
- Photoshop CS2/CS5/CS6/CC2015/CC2018/CC2017/CC2019软件安装及按照包
- Android WIFI调试助手源码分析
- 什么叫克隆人_什么叫克隆人?克隆技术有什么好处,快来涨姿
- 中职学校计算机运用基础试题,中职计算机基础试题(计算机一级)
- GitLab CI/CD Variables 中文文档
- The Road to multipath QUIC: 阿里自研多路径传输技术XLINK
- jspm律所应用管理系统毕业设计(附源码、运行环境)
- Kafka Connect使用教程
- linux命令系列 alias,Linux命令整合之alias