版權宣告:本文為博主原創文章,未經博主允許不得轉載
Github:github.com/AnliaLee
大家要是看到有錯誤的地方或者有啥好的建議,歡迎留言評論
前言:前一段時間公司服務端開發人手不足,而專案急需對接某個平臺的簡訊傳送介面,於是我便攬下了這個任務。看了看原來簡訊傳送的工具類程式碼,發現好幾個平臺的簡訊介面方法都堆在一起了,相互之間僅以方法名作為區分,整個工具類好幾百行程式碼,無論是呼叫相關方法還是維護原有程式碼,又或者是整合新的簡訊平臺都十分不方便,於是決定一邊學習物件導向設計的知識,一邊動手重構簡訊工具類。本期就以市面上幾款常見的簡訊介面為例子,聊一聊這種單一功能(傳送簡訊)但有多種方案(多平臺)的工具類的封裝過程,希望能對大家專案開發和功能整合有所啟發
封裝之前的準備工作
在開始動手之前,我們需要考慮怎樣去設計這個工具類。首先得明確為什麼要進行封裝
封裝的目的
- 讓呼叫簡訊工具類的使用者儘可能地降低使用成本
- 讓後續維護工具類的開發人員能夠更加方便地整合其他簡訊平臺和維護原有的程式碼
按照第一點要求,那麼我希望最終呼叫這個簡訊工具類時只有一個統一的入口,然後只需要簡單地通過一個引數選擇簡訊平臺,按該平臺要求傳入相應的引數即可完成簡訊傳送的操作以及獲取返回資料。考慮到各個平臺傳參的命名都不一樣,且可能有自定義的功能擴充套件,因此決定使用Builder模式去設計工具類入口
再來看第二點要求,如果像原來那樣將所有的簡訊平臺介面都放到同一個工具類中,各種解析函式也放在這裡面,那維護起來將異常麻煩,還可能會因為整合新的簡訊平臺而引入未知的BUG,因此我們需要將各平臺對接程式碼隔離開來
那麼分析了簡訊工具類如何入手開發之後,根據“自上而下設計,自下而上實現”的思想,我們從各平臺簡訊介面的整合開始一步步實現這個工具類
抽象整合簡訊平臺介面
原有的簡訊工具類中整合了阿里雲、網易雲、雲信等平臺的簡訊傳送介面,它們有的只能使用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,實現具體的請求過程
/**
* 阿里雲簡訊平臺
*/
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";//外部流水擴充套件欄位
}
複製程式碼
其他簡訊平臺的整合也是如此,就不多贅述了
實現構建Builder類和中介軟體Request類
使用者在使用我們的封裝工具類時,無需知道簡訊介面具體是如何呼叫的,隱藏簡訊介面呼叫過程可以避免使用者呼叫錯誤時引發的一系列問題,能夠有效地降低使用者使用成本。因此我們建立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,程式碼比較簡單,這裡使用了單例模式例項化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才能學到更多的東西,共勉~