談Android中DTO -> VO的重要性

標(biāo)題雖然僅指DTO->VO搀玖,其實(shí)更準(zhǔn)確的說(shuō),應(yīng)該是各種DTO驻呐、DAO等都需要轉(zhuǎn)VO 灌诅,本文僅以DTO為例。


不管你在使用MVC含末,MVP還是MVVM猜拾,這篇文章會(huì)讓你的M層賦有更佳的職能。

Clean架構(gòu)的Mapper

在去年嘗試Android-CleanArchitecture時(shí)佣盒,data模塊和presentation模塊里有2個(gè)Mapper類挎袜,用于把UserEntity轉(zhuǎn)成User,以及User轉(zhuǎn)成UserModel肥惭,最終V層使用的是UserModel對(duì)象盯仪。

當(dāng)時(shí)很難理解的是為何一個(gè)User要轉(zhuǎn)來(lái)轉(zhuǎn)去,現(xiàn)在回頭來(lái)看蜜葱,對(duì)象的映射轉(zhuǎn)換是一個(gè)良好的架構(gòu)所必需的要素全景,不管是MVC,MVP還是MVVM牵囤。

VO 爸黄、DTO在Android

1、 VO

value object值對(duì)象
ViewObject表現(xiàn)層對(duì)象

主要對(duì)應(yīng)界面顯示的數(shù)據(jù)對(duì)象揭鳞。對(duì)于一個(gè)WEB頁(yè)面炕贵,或者SWT、SWING的一個(gè)界面野崇,用一個(gè)VO對(duì)象對(duì)應(yīng)整個(gè)界面的值鲁驶。

Android中即:UI層的View需要的對(duì)象。

2舞骆、 DTO

Data Transfer Object數(shù)據(jù)傳輸對(duì)象

指用于展示層與服務(wù)層之間的數(shù)據(jù)傳輸對(duì)象钥弯。

Android中即:接口中返回的數(shù)據(jù)結(jié)構(gòu)JSON轉(zhuǎn)換后的對(duì)象径荔。

痛點(diǎn)

可能我們一般會(huì)忽略VO層,只有DTO層脆霎,即:View展示的Model對(duì)象和接口返回的Model是同一個(gè)對(duì)象总处,即DTO。

這樣做看起來(lái)睛蛛,省略了VO層也沒(méi)什么問(wèn)題鹦马,但是我告訴你,在開(kāi)發(fā)過(guò)程的所遇到的一些痛點(diǎn)忆肾,都和缺少VO層脫離不了關(guān)系:

1荸频、你是否經(jīng)常擔(dān)心View在使用Model時(shí),Model或Model的某個(gè)字段為null?

什么客冈?你說(shuō)你在C / P層或者domain中進(jìn)行了安全性的null校驗(yàn)旭从?

但是你不覺(jué)得在C/P或domain層處理這種安全校驗(yàn)是一種“臟代碼”嗎? 這樣會(huì)加重C/P或domain層的負(fù)擔(dān)场仲,降低其可讀性和悦。

這種 “糙活” 應(yīng)該是由更上游的M層來(lái)處理!

2渠缕、接口返回的數(shù)據(jù)結(jié)構(gòu)或字段突然變更了8胨亍!

這太常見(jiàn)了R嗔邸馍忽!需求變更等等因素都會(huì)引起這個(gè)問(wèn)題。

你在改變DTO字段的同時(shí)燕差,還要同時(shí)改變所以使用該DTO的View處的代碼遭笋!

3、接口返回的數(shù)據(jù)結(jié)構(gòu)不是我想要的啊!<勐薄牡属!

因?yàn)楹笈_(tái)人員的各種原因,導(dǎo)致接口返回的數(shù)據(jù)結(jié)構(gòu)根本不是我們想要的谎替,而你在和后臺(tái)撕逼失敗后偷溺,你是否在苦惱該如何處理這蛋疼的數(shù)據(jù)結(jié)構(gòu)?

更痛苦的是這條和第2條一并出現(xiàn)時(shí)钱贯,我想你一定是這樣的:

4挫掏、接口文檔遲遲沒(méi)有!秩命!

在開(kāi)發(fā)過(guò)程中尉共,你的View已經(jīng)寫好褒傅,但是接口文檔遲遲還沒(méi)有給出,即使給出袄友,變更的幾率也會(huì)非常大殿托!

即使你們有接口評(píng)審這個(gè)環(huán)節(jié),但是仍不能100%保證最后的數(shù)據(jù)結(jié)構(gòu)和字段名稱都是像接口評(píng)審時(shí)敲定的那樣剧蚣!

上述的4個(gè)痛點(diǎn)支竹,我相信在你的開(kāi)發(fā)過(guò)程中經(jīng)常會(huì)遇到,而解決之道在于:

你需要添加一個(gè)VO層鸠按,以及DTO->VO層的映射Mapper 礼搁!

一個(gè)映射例子

有這樣一個(gè)接口:

@GET
Observable<UserListDTO> getUserList()

public final class UserListDTO {
   public int version;  // 一個(gè)與View無(wú)關(guān)的屬性
   public List<UserDTO> userList;

   static class UserDTO {
      public String uid;
      public String user_name;
      public String gender;
   }
}

你需要拿到UserList然后展現(xiàn)到RecyclerView上。

但是對(duì)于View層來(lái)說(shuō):

  • 我不想知道你的version是干嘛的
  • 我只需要一個(gè)List<User>目尖,User里有id馒吴,name以及男or女即可
  • 我不想要什么uiduser_name卑雁,gender等這樣后臺(tái)強(qiáng)加給我的命名或數(shù)據(jù)類型(即便我們有GSON的@SerializedName注解)
  • 更重要的是我不想拿到一個(gè)為null的userList募书,
  • VO層

    public final class User{
       public String id;
       public String name;
       public boolean isMale;
    }

    Mapper

    public interface Mapper<T>{
       T transform();
    }

    轉(zhuǎn)換過(guò)程:

    public final class UserListDTO implements Mapper<List<User>>{
       public int version;
       public List<UserListDTO.UserDTO> userList;
    
       @Override
       public List<User> transform() {
          List<User> list = new ArrayList<>();
          for(UserDTO dto : userList){
             list.add(dto.transform);
          }
          return list;
      }
    
      private static class UserDTO implements Mapper<User>{
         public String uid;
         public String user_name;
         public String gender;
    
         @Override
         public User transform() {
           User user = new User();
           user.id = uid;
           user.name = user_name == null ? "未知" : user_name;
           isMale = "0".equals(gender);
           return user;
         }
      }
    }

    使用

    Api.getService().getUserList()    // Retrofit
                      ....
                    .map(new Func1<UserListDTO, List<User>() {
                        @Override
                        public String call(UserListDTO dto) {
                            return dto.transform;
                        }
                    })
                    .subscribe(...) // 將List<User>指派給V層

    如何解決痛點(diǎn)的?

  • 痛點(diǎn)1:M或M的屬性為null测蹲?
    V層不用擔(dān)心會(huì)不會(huì)拿到的是null莹捡,因?yàn)橐呀?jīng)在DTO的transform()進(jìn)行安全處理了,在null時(shí)也會(huì)返回size=0的List<User>扣甲。
    同時(shí)對(duì)User中的name也進(jìn)行了安全性檢查篮赢。

  • 痛點(diǎn)2:接口返回的數(shù)據(jù)結(jié)構(gòu)或字段突然變更
    使用這種Mapper之后,大多數(shù)情況琉挖,你只需要更改DTO里的transform()的實(shí)現(xiàn)即可启泣。

    比如UserDTO的user_name要更改為userName,或者gender不再是"0"為男性等等更改示辈,你只需要在DTO里做如下更改即可:

    @Override 
    public User transform() {
       ...
       user.name = userName;
    }

    VO以及View使用VO的地方則無(wú)需更改寥茫!


  • 痛點(diǎn)3:接口返回的數(shù)據(jù)結(jié)構(gòu)不是我想要的?
    沒(méi)關(guān)系矾麻,把它轉(zhuǎn)成我們想要的VO對(duì)象即可纱耻,上文中gender對(duì)View來(lái)說(shuō)只需要是男or女即可,不需要知道“gender的值為0時(shí)是男性”這種業(yè)務(wù)邏輯险耀。

    PS: 上述只是一個(gè)簡(jiǎn)單例子弄喘,實(shí)際場(chǎng)景中,不符合預(yù)期的數(shù)據(jù)結(jié)構(gòu)可能要復(fù)雜很多


  • 痛點(diǎn)4:接口文檔遲遲沒(méi)有
    真實(shí)開(kāi)發(fā)過(guò)程中甩牺,我們會(huì)先拿到設(shè)計(jì)稿蘑志,有了設(shè)計(jì)稿后臺(tái)才開(kāi)始寫接口文檔。
    但是如果我們進(jìn)度太超前,或者某種原因后臺(tái)人員投入時(shí)間過(guò)晚急但,此時(shí)我們的接口文檔拿到會(huì)比較晚澎媒。

    如果我們就這樣等著后臺(tái)人員豈不很蠢?羊始!

    好好利用VO把旱幼!其實(shí)我們根據(jù)設(shè)計(jì)稿就可以明確知道,各個(gè)頁(yè)面所需要的數(shù)據(jù)模型突委,此時(shí)只要?jiǎng)?chuàng)建一個(gè)我們自己需要的VO對(duì)象柏卤,而View使用該VO展現(xiàn)即可。
    在真正的DTO出來(lái)之前匀油,你可以用VO來(lái)mock數(shù)據(jù)缘缚、接口;一旦DTO出來(lái)敌蚜,只要在DTO內(nèi)做一個(gè)Mapper即可桥滨,VO以及View一般不需要更改!

  • M層還可以做更多:

    假如現(xiàn)在的需求是弛车,User的性別不同齐媒,在RecyclerView中顯示的View就不同,即屬于不同的ItemType纷跛,你可能會(huì)把這個(gè)判斷寫在:

     @Override
     public int getItemViewType(int position) {
        User user = mDatas.get(position);
        return user.male ? 0 : 1;
     }

    雖然看起來(lái)沒(méi)多少代碼喻括,但是實(shí)際情況我們遇到的需求往往會(huì)很復(fù)雜,getItemViewType()里需要寫不少邏輯代碼贫奠,這時(shí)你的Adapter里充斥了一些“臟代碼”唬血。

    V層應(yīng)該專注V應(yīng)該做的事,而不是數(shù)據(jù)的處理唤崭、業(yè)務(wù)邏輯拷恨!

    請(qǐng)放到Mapper里做:

    public final class User{
       ...
       public int itemType;
    }

    private static class UserDTO implements Mapper<User>{
         @Override
         public User transform() {
            ...
            user.itemType = user.isMale ? 0 : 1;
            return user;
          }
      }

    總結(jié):

  • 從實(shí)用角度出發(fā):Mapper可以讓你的開(kāi)發(fā)過(guò)程更高效,更從容面對(duì)需求變更谢肾、接口變更等外在因素腕侄。

  • 從架構(gòu)角度出發(fā):Mapper的出現(xiàn)可以讓M層可以承擔(dān)更多的職責(zé),來(lái)減輕中間層的壓力芦疏,更利于構(gòu)造一個(gè)V冕杠,M的分離的架構(gòu)。

  • PS:文中轉(zhuǎn)換的方式是構(gòu)建Mapper接口眯分,使用起來(lái)很方便拌汇。
    你也可以像Android-CleanArchitecture一樣創(chuàng)建獨(dú)立的Mapper類:在Mapper類進(jìn)行transform柒桑,優(yōu)點(diǎn)是可以保持DTO與VO的干凈和獨(dú)立弊决;

    最后編輯于
    ?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
    • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子飘诗,更是在濱河造成了極大的恐慌与倡,老刑警劉巖,帶你破解...
      沈念sama閱讀 221,273評(píng)論 6 515
    • 序言:濱河連續(xù)發(fā)生了三起死亡事件昆稿,死亡現(xiàn)場(chǎng)離奇詭異纺座,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)溉潭,發(fā)現(xiàn)死者居然都...
      沈念sama閱讀 94,349評(píng)論 3 398
    • 文/潘曉璐 我一進(jìn)店門净响,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人喳瓣,你說(shuō)我怎么就攤上這事馋贤。” “怎么了畏陕?”我有些...
      開(kāi)封第一講書(shū)人閱讀 167,709評(píng)論 0 360
    • 文/不壞的土叔 我叫張陵配乓,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我惠毁,道長(zhǎng)犹芹,這世上最難降的妖魔是什么? 我笑而不...
      開(kāi)封第一講書(shū)人閱讀 59,520評(píng)論 1 296
    • 正文 為了忘掉前任鞠绰,我火速辦了婚禮腰埂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘洞豁。我一直安慰自己盐固,他們只是感情好,可當(dāng)我...
      茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
    • 文/花漫 我一把揭開(kāi)白布丈挟。 她就那樣靜靜地躺著刁卜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪曙咽。 梳的紋絲不亂的頭發(fā)上蛔趴,一...
      開(kāi)封第一講書(shū)人閱讀 52,158評(píng)論 1 308
    • 那天,我揣著相機(jī)與錄音例朱,去河邊找鬼孝情。 笑死,一個(gè)胖子當(dāng)著我的面吹牛洒嗤,可吹牛的內(nèi)容都是我干的箫荡。 我是一名探鬼主播,決...
      沈念sama閱讀 40,755評(píng)論 3 421
    • 文/蒼蘭香墨 我猛地睜開(kāi)眼渔隶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼羔挡!你這毒婦竟也來(lái)了洁奈?” 一聲冷哼從身側(cè)響起,我...
      開(kāi)封第一講書(shū)人閱讀 39,660評(píng)論 0 276
    • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤绞灼,失蹤者是張志新(化名)和其女友劉穎利术,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體低矮,經(jīng)...
      沈念sama閱讀 46,203評(píng)論 1 319
    • 正文 獨(dú)居荒郊野嶺守林人離奇死亡印叁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
      茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
    • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了军掂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片轮蜕。...
      茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
    • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蝗锥,靈堂內(nèi)的尸體忽然破棺而出肠虽,到底是詐尸還是另有隱情,我是刑警寧澤玛追,帶...
      沈念sama閱讀 36,122評(píng)論 5 349
    • 正文 年R本政府宣布税课,位于F島的核電站,受9級(jí)特大地震影響痊剖,放射性物質(zhì)發(fā)生泄漏韩玩。R本人自食惡果不足惜,卻給世界環(huán)境...
      茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
    • 文/蒙蒙 一陆馁、第九天 我趴在偏房一處隱蔽的房頂上張望找颓。 院中可真熱鬧,春花似錦叮贩、人聲如沸击狮。這莊子的主人今日做“春日...
      開(kāi)封第一講書(shū)人閱讀 32,272評(píng)論 0 23
    • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)彪蓬。三九已至,卻和暖如春捺萌,著一層夾襖步出監(jiān)牢的瞬間档冬,已是汗流浹背。 一陣腳步聲響...
      開(kāi)封第一講書(shū)人閱讀 33,393評(píng)論 1 272
    • 我被黑心中介騙來(lái)泰國(guó)打工桃纯, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酷誓,地道東北人。 一個(gè)月前我還...
      沈念sama閱讀 48,808評(píng)論 3 376
    • 正文 我出身青樓态坦,卻偏偏與公主長(zhǎng)得像盐数,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子伞梯,可洞房花燭夜當(dāng)晚...
      茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

    推薦閱讀更多精彩內(nèi)容