高質量App的架構設計與思考!

最近在做一功能不大、業務也不複雜的小眾App,以往做App是發現自己從來沒有考慮過一些架構方面的問題,只是按照自己以往的習慣去寫代碼,忽略了App的設計。本次分享主要包含一些開發App的小經驗和技巧,來一次App開發與設計的分享。

先和分享下一下實體類的設計與組織形式

實體類的組織

在做App開發的時候有很多的實體類,項目越複雜實體類就會越多,經過我的一番思考大致這可以將實體分為以下幾大數:

  • 面向數據庫的
  • 服務端返回的數據實體
  • 用於渲染View的實體(使用Databinding)

一般情況下實體類的操作會經過以下步驟:

  1. App請求服務器獲取數據
  2. 將數據存入數據庫(可選)
  3. 渲染頁面展示數據

現在的實體的產生只用在請求服務器數據的時候才需要新建,後續的數據庫、頁面渲染其實是可以使用一套實體:

先不說這樣做的行不行,首先三個地方使用同一實體就會引起字段歧義比如服務器數據有Id、本地數據也有Id,那兩個id字段就有衝突了不得不改字段名。

另一種情況渲染和數據本身並不會一一對應,有時候後端數據給的是一個純数字而前端頁面显示的是字符串兩個都對應不上,強行放在一起會起來更多的問題。

所為實體類的的正確組織形式應該是:相互隔離、互不干擾

數據實體的在渲染之前都需要準備好,比如在ViewModel中將int型的數據轉換成文本型的數據然後再使用Databinding+頁面渲染實體來渲染頁面。

優雅的處理網絡數據

現在Android開發使用的網絡庫大部分都是Okhttp + Retrofit,使用Retrofit網絡交互變的非常簡單一個Service接口就能搞定一切,美茲茲~~,現在大部分後端返回的數據都會是以下形式:

{
    "code":0,
    "data": {},
    "msg": ""
}

雖然不能涵蓋所有,但還是可以非常贊的數據、消息、成功與否啥都有!對於前面主要是關注data字段,其他msgcode等都屬於輔助字段。前端對應的實體對象應該是這樣的(假代碼):

public class ApiResponse<T> {
    private int code;
    private T data;
    private String msg;
}

對應的Service那就得定義成這樣(使用了RxJava):

public intface UserService {
    @GET("/xx/{id}")
    Single<ApiResponse<UserInfo> getUserInfoById(@Path("id") Long userId);
}

從接口中可以看出來,方法的返回值就包了幾層,如果要拿data字段需要經過:ApiResponse -> UserInfo,而且在拿之前還要判斷code字段:


...

if(ApiResponse.code == 0){
    UserInfo info = ApiResponse.getData();
}

...

為了消除這些冗餘的代碼可以使用CallAdapter來使Service方法返回的數據直接就是實體類:

public intface UserService {
    @GET("/xx/{id}")
    Single<UserInfo> getUserInfoById(@Path("id") Long userId);
}

CallAdapter的代碼就不貼了,可以自行查找。這樣做帶來的另外一個問題就是業務代碼如何判斷接口是否成功或失敗,前端必需友好的把錯誤提示給用戶而不是一直搞個Loading在那裡瞎轉~~。現階段最方便的的錯誤傳遞方式是使用Java異常,前端可以定義業務異常網絡異常

public class BizException extends RuntimeException {
    ...
}

CallAdapter中檢查ApiResponse的返回值是否成功:


if(!ApiResponse != 0){
    throw new BizExcepiton(ApiResponse);
}

如果後端返回業務異常那前端就對應拋出一個BizExcepiton,如果是http錯誤如:404、400那可以拋出HttpException。除了BizExcepitonHttpException外還可使用特定的異常比如後端返回密碼錯誤異常:

public class InvalidPasswordException extends BizException {
    ...
}

如需特殊處理,也可以滿足要求。

健壯的數據層

現在很多應用都開發使用MVVM開發模式數據層都使用Repository來表示,面向數據驅動的開發模式,頁面變化都需要隨着數據變更而更新,數據發生變化然後頁面再做出響應。Repository的拆分要細一點,不建議簡單的弄個UserRepository包含登陸、註冊、更新密碼等等操作,設計Repository的一些想法:

  1. 面向接口編程
  2. 保持單一原則
  3. 功能邊界要清晰(如:登陸、註冊可以分開)
  4. 業務邏輯盡可能的少(複雜的業務考慮Presenter)

一個判斷是否是好的設計的辦法可以這樣:一個登陸頁面從Activive/Fragment到ViewModel再到Repository,有沒有多餘的代碼。比如上面說的UserRepository包含登陸、註冊但是在一個登陸頁面就不需要有註冊功能,從登陸頁面上來看註冊的代碼就是多餘的(有些App登陸/註冊在一個頁面的~~)。

一個包含登陸、註冊的UserRepository簡單圖:

另外一點是盡量將repository使用到的一些東西集中管理,可引入一個基礎的repository:

public class SimpleRepository {
    
    protected final  <T> T getService(Class<T> clz){
        return Services.getService(clz);
    }
}

做為SimpleRepository的子類,就不需要考慮從哪裡獲取service的問題。

簡潔的UI層

UI層面可以分為ViewModel和View(Activity/Fragment), View的職責應當只有二點:

  1. 展示業務數據
  2. 收集業務數據

例如一些數據的組織、判斷都不應該出現在View中比如:

 if (Strings.isNullOrEmpty(phone)) {
       ...
        return;
 }

 if (Strings.isNullOrEmpty(pwd)) {
        ...
        return;
  }

像上面這類的代碼都不應該出現在View中,而在放置在ViewModel裏面,View只收集用戶數據傳遞給ViewModel由它來進行數據校驗。再比如像這樣的if/else代碼也應該放置在ViewModel中:

 int age = 10;
 String desc = "";
 if(age < 18){
    desc = "青年";
 }else if(age < 29){
    desc = "中年";
 }

如果數據的显示和數據的收集過多,建議使用Databinding來進行雙向綁定數據。再搭配LiveData使View作為觀察者實時監聽數據變化:

registerViewModel.getRegistryResult().observe(this, new SimpleObserver<RegistryInfo>(this));

一旦數據發生變化LiveData就會通知Observer更新,通過DataBinding更新各個頁面數據。

再說ViewModel應該只包含一些簡單的判斷、檢查、打通數據的代碼,如果業務過於複雜可以考慮加Presetner,如果真的超級複雜那可以反思下這個複雜的邏輯應不應該放在前端,能不能放在後端呢?

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!