版权声明:本文为博主原创文章,未经博主容许不得转载html
Github:github.com/AnliaLeejava
你们要是看到有错误的地方或者有啥好的建议,欢迎留言评论git
前言:前一段时间公司服务端开发人手不足,而项目急需对接某个平台的短信发送接口,因而我便揽下了这个任务。看了看原来短信发送的工具类代码,发现好几个平台的短信接口方法都堆在一块儿了,相互之间仅以方法名做为区分,整个工具类好几百行代码,不管是调用相关方法仍是维护原有代码,又或者是集成新的短信平台都十分不方便,因而决定一边学习面向对象设计的知识,一边动手重构短信工具类。本期就以市面上几款常见的短信接口为例子,聊一聊这种单一功能(发送短信)但有多种方案(多平台)的工具类的封装过程,但愿能对你们项目开发和功能集成有所启发github
在开始动手以前,咱们须要考虑怎样去设计这个工具类。首先得明确为何要进行封装json
封装的目的
- 让调用短信工具类的用户尽量地下降使用成本
- 让后续维护工具类的开发人员可以更加方便地集成其余短信平台和维护原有的代码
按照第一点要求,那么我但愿最终调用这个短信工具类时只有一个统一的入口,而后只须要简单地经过一个参数选择短信平台,按该平台要求传入相应的参数便可完成短信发送的操做以及获取返回数据。考虑到各个平台传参的命名都不同,且可能有自定义的功能扩展,所以决定使用Builder模式去设计工具类入口异步
再来看第二点要求,若是像原来那样将全部的短信平台接口都放到同一个工具类中,各类解析函数也放在这里面,那维护起来将异常麻烦,还可能会由于集成新的短信平台而引入未知的BUG,所以咱们须要将各平台对接代码隔离开来ide
那么分析了短信工具类如何入手开发以后,根据“自上而下设计,自下而上实现”的思想,咱们从各平台短信接口的集成开始一步步实现这个工具类函数
原有的短信工具类中集成了阿里云、网易云、云信等平台的短信发送接口,它们有的只能使用post进行数据传输,有的post、get两种方式均可以,所以咱们进行抽象时须要将这两种传输方式都考虑进来,建立SMSModel抽象类工具
public abstract class SMSModel {
public abstract String post(Map<String, String> map);
public abstract String get(Map<String, String> map);
}
复制代码
以阿里云的短信发送接口为例,新建ALModel继承SMSModel,实现具体的请求过程post
/** * 阿里云短信平台 */
public class ALModel extends SMSModel{
@Override
public String post(Map<String, String> map){
//省略具体的代码实现...
return result;
}
@Override
public String get(Map<String, String> map) {//由于该平台不支持get方式传输数据,直接返回错误信息便可
return "请求失败!该平台不支持get请求";
}
}
复制代码
接着按照阿里云短信平台的开发文档,按照入参列表建立SMSParameter,便于用户注入参数
public class SMSParameter {
//短信接口平台
public static final String SMS_MODEL_AL = "AL";//阿里云短信平台
//阿里云短信接口参数,文档:https://help.aliyun.com/document_detail/55284.html?spm=5176.doc55289.6.557.J43llA
public static final String AL_KEYID = "AccessKeyId";
public static final String AL_KEYSECRET = "AccessKeySecret";
public static final String AL_PHONENUMBERS = "PhoneNumbers";//短信接收号码
public static final String AL_SIGNNAME = "SignName";//短信签名
public static final String AL_TEMPLATECODE = "TemplateCode";//短信模板ID
public static final String AL_TEMPLATEPARAM = "TemplateParam";//短信模板变量替换JSON串
public static final String AL_SMSUPEXTENDCODE = "SmsUpExtendCode";//上行短信扩展码
public static final String AL_OUTID = "OutId";//外部流水扩展字段
}
复制代码
其余短信平台的集成也是如此,就很少赘述了
用户在使用咱们的封装工具类时,无需知道短信接口具体是如何调用的,隐藏短信接口调用过程能够避免用户调用错误时引起的一系列问题,可以有效地下降用户使用成本。所以咱们建立SMSBuilder类用于管理和配置用户调用工具类的传参,实现工具类的自由扩展和构建。建立SMSRequest类用于链接SMSBuilder和SMSModel,起到中间桥梁的做用
先来看SMSBuilder类,咱们暂定几个用于初始化工具类的参数,使用枚举(ModelType)限制用户可选用的短信平台,并在setModelType方法中根据用户的选择实例化相应的SMSModel,最后当用户使用build()方法完成工具类初始化时实例化一个SMSRequest类执行调用短信接口的操做。SMSBuilder代码以下
public abstract class SMSBuilder {
public String builderType;//builder类型,分为post和get
public String modelType;//用户选择的短信平台
public Map<String, String> map;//保存短信平台的传参
public SMSModel smsModel;
/** * 短信平台: * SMS_MODEL_AL(阿里短信平台) */
public enum ModelType{
SMS_MODEL_AL
}
public SMSBuilder() {}
public SMSBuilder setModelType(ModelType modelType) {
if (modelType != null) {
switch (modelType) {
case SMS_MODEL_AL:
this.modelType = SMSParameter.SMS_MODEL_AL;
smsModel = new ALModel();
break;
default:
this.modelType = "";
break;
}
}
return this;
}
public SMSBuilder addMapParams(Map<String, String> map){
if(this.map == null){
this.map = map;
}
return this;
}
public SMSRequest build(){
return new SMSRequest(this);
}
}
复制代码
SMSRequest类根据用户利用SMSBuilder初始化的参数执行具体的调用接口操做(toRequest),代码以下
public class SMSRequest {
private SMSModel smsModel;
private String builderType;
private String modelType;
private Map<String, String> paramsMap;
private String result;
private String errorMessage;
public SMSRequest(SMSBuilder builder){
builderType = builder.builderType;
modelType = builder.modelType;
paramsMap = builder.map;
smsModel = builder.smsModel;
result = "";
errorMessage = modelType+":"+builderType;
if(builder.modelType == null || builder.modelType.equals("")){
result = builderType+"请求失败!短信接口类型不能为空";
return;
}
toRequest();
}
/** * 同步获取返回数据 */
public String execute(){
return result;
}
private void toRequest(){
if(paramsMap==null){
result = errorMessage+"请求失败!map不能为空!";
return;
}
if(builderType.equals("post")){
result = smsModel.post(paramsMap);
}else if(builderType.equals("get")){
result = smsModel.get(paramsMap);
}
}
}
复制代码
这里仅以最基础的功能配置为例,若你们须要扩展更多的功能例如超时提醒、群发短信或异步接收回参等能够在此基础上修改
最后再来看下用户直接接触到的类SMSUtils,代码比较简单,这里使用了单例模式实例化SMSUtils,并在其中定义了PostBuilder和GetBuilder用于区分post和get请求,具体代码以下
public class SMSUtils {
private volatile static SMSUtils mInstance;
private SMSUtils(){}
private static class SMSUtilsHolder{
private static final SMSUtils mInstance = new SMSUtils();
}
public static SMSUtils getInstance(){
return SMSUtilsHolder.mInstance;
}
public class PostBuilder extends SMSBuilder{
public PostBuilder(){
this.builderType = "post";
}
}
public class GetBuilder extends SMSBuilder{
public GetBuilder(){
this.builderType = "get";
}
}
public static PostBuilder post(){
return getInstance().new PostBuilder();
}
public static GetBuilder get(){
return getInstance().new GetBuilder();
}
}
复制代码
完成整个工具类的封装后,用户之后调用短信发送接口时只须要像下面示例那样简单写几行代码便可
String result = "";
Map<String, String> map = new HashMap<String, String>();
map.put(SMSParameter.XXX, 参数内容);
...//按照平台要求配置相应参数
result = SMSUtils
.post()// post():请求类型为post,get():请求类型为get
.setModelType(ModelType.SMS_MODEL_AL)// 选择短信平台
.addMapParams(map)// 注入参数map
.build()// 完成初始化
.execute();
System.out.println("result:"+result);
复制代码
整个工具类的目录结构以下,其中SMSUnitTest用于单元测试,model目录下存放各短信平台的具体实现类,parameter目录用于存放公用的参数常量类,utils目录用于存放某些须要用到的工具类如xml、json解析类等
整个工具类介绍完了,回到咱们一开始提出的两点封装目的,咱们的工具类是否有效下降了用户的使用成本?我以为相对于在几百上千行的工具类中查找须要使用的短信接口来讲,是的。Builder模式链式结构的初始化过程能让用户调用咱们的工具类更加顺手且代码逻辑清晰、易读性好。那么是否下降了维护人员的开发成本呢?咱们以集成新的短信平台为例,维护人员只须要在model包下建立新的平台实现类,而后在SMSBuilder中配置相应的参数便可,大大下降了工具类的耦合度,也减小了多人开发形成冲突的可能性。若某个平台的对接出现BUG,仅须要在相应的实现类中DEBUG便可
本期博客到这里就结束了,因为我我的能力有限,有些地方确定作得还不够好,若你们有什么建议欢迎留言指出,不断地写BUG再修复BUG才能学到更多的东西,共勉~