標(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
是干嘛的id
馒吴,name
以及男or女
即可uid
,user_name
卑雁,gender
等這樣后臺(tái)強(qiáng)加給我的命名或數(shù)據(jù)類型(即便我們有GSON的@SerializedName
注解)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)的?
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ú)需更改寥茫!
沒(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ú)立弊决;