版權聲明:本文爲博主原創文章,未經博主容許不得轉載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才能學到更多的東西,共勉~