為什么你需要ViewObject

WhyNeedViewObject.png

作者:李旺成###

時(shí)間:2016年4月12日###


這里使用了一個(gè)解析當(dāng)前天氣 JSON 字符串得到原始 Model 后蠕搜,將該 Model 的數(shù)據(jù)展示到一個(gè)簡(jiǎn)單的頁面上來進(jìn)行演示祥得。

先看下 Demo 的效果圖:


天氣展示 Demo

我理解的 VO

VO杠袱,ViewObjectViewModel。關(guān)于它的解釋在 Android MVP 詳解(下)中烂斋,我做過簡(jiǎn)要的闡述手负。這里无牵,再說說我是怎么理解 VO 的飞蛹。

VO须肆,就是一切給 View 提供數(shù)據(jù)的對(duì)象。這個(gè)定義就很廣泛了桩皿,所以我對(duì) VO 做了如下的分類(下面會(huì)細(xì)說)。

VO 的實(shí)現(xiàn)方式

既然幢炸,所有給 View 提供數(shù)據(jù)的對(duì)象都可以稱之為 VO泄隔,那么 VO 的來源或者說形式就很多了。我在這里根據(jù) VO 的實(shí)現(xiàn)方式進(jìn)行了分類宛徊,僅僅是一家之言佛嬉,有疏漏之處逻澳,見諒。

1. 單獨(dú)的 VO 類

Android MVP 詳解(下)中建議專門建一個(gè)包 vo暖呕,用來存放該模塊下的所有 VO 類斜做。對(duì)于這一類,那就屬于單獨(dú)的 VO 類湾揽,或者更準(zhǔn)確的說明是“獨(dú)立的 VO 類”瓤逼。

要使用這種類型的 VO,有一個(gè)問題库物,它是獨(dú)立的類霸旗,那么就需要另外的對(duì)象給它提供數(shù)據(jù)。在這里我認(rèn)為提供(傳遞)數(shù)據(jù)的方式戚揭,大致有如下兩種:

A. 使用轉(zhuǎn)換器

專門使用一個(gè)轉(zhuǎn)換器類诱告,來做原始 Model 到 VO 的轉(zhuǎn)換。如示例項(xiàng)目中的 VOConverterUtil.java 類民晒。(在這類里偷了個(gè)懶精居,直接調(diào)用了“構(gòu)造方法中轉(zhuǎn)換”的方式進(jìn)行了轉(zhuǎn)換)
還是看下代碼吧:

public class VOConverterUtil {
    public static WeatherVO getWeatherVOFromWeatherBean(WeatherBean weatherBean) {
        // 這里偷個(gè)懶
        WeatherVO weatherVO = new WeatherVO(weatherBean);
        return weatherVO;
    }
}

B. 構(gòu)造方法中轉(zhuǎn)換

這個(gè)很好理解,就是在構(gòu)造方法中進(jìn)行數(shù)據(jù)轉(zhuǎn)換潜必。代碼很簡(jiǎn)單靴姿,直接看代碼:

public WeatherVO(WeatherBean weatherBean) {
    if (weatherBean == null) return;
    isSuccess = "ok".equals(weatherBean.getStatus());
    int condCode = Integer.parseInt(weatherBean.getNow().getCond().getCode());
    String condCodeColorStr = "";
    if (condCode < 0) {
        weatherInfoIcon = R.mipmap.ic_snow;
        condCodeColorStr = "#000066";
    } else if (condCode < 60) {
        weatherInfoIcon = R.mipmap.ic_rain;
        condCodeColorStr = "#009900";
    } else if (condCode < 90) {
        weatherInfoIcon = R.mipmap.ic_cloudy;
        condCodeColorStr = "#993300";
    } else {
        weatherInfoIcon = R.mipmap.ic_sunshine;
        condCodeColorStr = "#cccc00";
    }
    weatherInfoText = Html.fromHtml("<font color='"+condCodeColorStr+"'>"+weatherBean.getNow().getCond().getTxt()+"</font>");
    relativeHumidity = "相對(duì)濕度:" + weatherBean.getNow().getHum();
    int tmpInt = Integer.parseInt(weatherBean.getNow().getTmp());
    if (tmpInt < 15) {
        temperatureIcon = R.mipmap.ic_lowtemperature;
    } else if (tmpInt < 33) {
        temperatureIcon = R.mipmap.ic_thermophilic;
    } else {
        temperatureIcon = R.mipmap.ic_hightemperature;
    }
    airPressure = "氣壓:" + weatherBean.getNow().getPres();
    precipitation = "降水量:" + weatherBean.getNow().getPcpn();
    visibility = "能見度:" + weatherBean.getNow().getVis() + " KM";
    windDirectionAngle = "風(fēng)向角度:" + weatherBean.getNow().getWind().getDeg();
    windDirection = "風(fēng)向:" + weatherBean.getNow().getWind().getDir();
    windPower = "風(fēng)力:" + weatherBean.getNow().getWind().getSc();
    windSpeed = "風(fēng)速" + weatherBean.getNow().getWind().getSpd();
}

2. 實(shí)現(xiàn)接口成 VO

抽出單獨(dú)的類,那么就多了一個(gè)類刮便, Modle 如果很多的話空猜,那不可避免 VO 的數(shù)量也會(huì)增加。有些人可能覺得沒必要恨旱,這增加了項(xiàng)目復(fù)雜度(哈哈辈毯,任何的設(shè)計(jì)都有可能造成復(fù)雜度上升)。那么搜贤,這樣谆沃,我們抽取一個(gè)接口,然后讓原始 Model 去實(shí)現(xiàn)這個(gè)接口 —— 以后就可以“面向接口編程”了仪芒。

思路很簡(jiǎn)單唁影,那么直接上代碼吧:
抽取接口 IWeatherVO.java:

public interface IWeatherVO {

    boolean isSuccess(); // "status": "ok", //接口狀態(tài)
    int getWeatherInfoIcon(); // "code": "100", //天氣狀況代碼 假設(shè) <0 下雪, < 60 雨掂名,大于 >60 < 90 陰据沈, > 90 晴
    Spanned getWeatherInfoText(); // "txt": "晴" //天氣狀況描述 天氣的文本描述
    String getRelativeHumidity(); //  "hum": "20%", //相對(duì)濕度(%)
    int getTemperatureIcon(); // "tmp": "32", //溫度 溫度圖標(biāo)
    String getAirPressure(); // "pres": "1001", //氣壓
    String getPrecipitation(); // 降水量
    String getVisibility(); // "vis": "10", //能見度(km)
    String getWindDirectionAngle(); // "deg": "10", //風(fēng)向(360度)
    String getWindDirection(); // "dir": "北風(fēng)", //風(fēng)向
    String getWindPower(); // "sc": "3級(jí)", //風(fēng)力
    String getWindSpeed(); // "spd": "15" //風(fēng)速(kmph)

}

實(shí)現(xiàn)接口

public class WeatherBean implements IWeatherVO {

    // 原始 Modle 中的字段都省略了,具體看源碼吧
    ...

    //==========實(shí)現(xiàn) VO 接口==========
    @Override
    public boolean isSuccess() {
        return "ok".equals(status);
    }

    @Override
    public int getWeatherInfoIcon() {
        int weatherInfoIcon;
        int condCode = Integer.parseInt(getNow().getCond().getCode());
        if (condCode < 0) {
            weatherInfoIcon = R.mipmap.ic_snow;
        } else if (condCode < 60) {
            weatherInfoIcon = R.mipmap.ic_rain;
        } else if (condCode < 90) {
            weatherInfoIcon = R.mipmap.ic_cloudy;
        } else {
            weatherInfoIcon = R.mipmap.ic_sunshine;
        }
        return weatherInfoIcon;
    }

    @Override
    public Spanned getWeatherInfoText() {
        Spanned weatherInfoText;
        int condCode = Integer.parseInt(getNow().getCond().getCode());
        String condCodeColorStr = "";
        if (condCode < 0) {
            condCodeColorStr = "#000066";
        } else if (condCode < 60) {
            condCodeColorStr = "#009900";
        } else if (condCode < 90) {
            condCodeColorStr = "#993300";
        } else {
            condCodeColorStr = "#cccc00";
        }
        weatherInfoText = Html.fromHtml("<font color='"+condCodeColorStr+"'>"+getNow().getCond().getTxt()+"</font>");
        return weatherInfoText;
    }

    @Override
    public String getRelativeHumidity() {
        return "相對(duì)濕度:" + getNow().getHum();
    }

    @Override
    public int getTemperatureIcon() {
        int temperatureIcon;
        int tmpInt = Integer.parseInt(getNow().getTmp());
        if (tmpInt < 15) {
            temperatureIcon = R.mipmap.ic_lowtemperature;
        } else if (tmpInt < 33) {
            temperatureIcon = R.mipmap.ic_thermophilic;
        } else {
            temperatureIcon = R.mipmap.ic_hightemperature;
        }
        return temperatureIcon;
    }

    @Override
    public String getAirPressure() {
        return "氣壓:" + getNow().getPres();
    }

    @Override
    public String getPrecipitation() {
        return "降水量:" + getNow().getPcpn();
    }

    @Override
    public String getVisibility() {
        return "能見度:" + getNow().getVis() + " KM";
    }

    @Override
    public String getWindDirectionAngle() {
        return "風(fēng)向角度:" + getNow().getWind().getDeg();
    }

    @Override
    public String getWindDirection() {
        return "風(fēng)向:" + getNow().getWind().getDir();
    }

    @Override
    public String getWindPower() {
        return "風(fēng)力:" + getNow().getWind().getSc();
    }

    @Override
    public String getWindSpeed() {
        return "風(fēng)速" + getNow().getWind().getSpd();
    }

}

3. 添加方法成 VO

這個(gè)就更簡(jiǎn)單了饺蔑,那就是連接口都不抽取了锌介,直接提供上述接口中的方法。這里就不贅述了,思路是和上面提取接口一致孔祸,所提供的方法隆敢,目的就是方便在 View 中直接使用。(這個(gè)在 Android MVP 詳解(下)中討論過崔慧,略)

4. 沒有 VO

沒有 VO拂蝎,那就是根本不使用 VO。如果你的項(xiàng)目是 MVP 的惶室,那么就在 Presenter 中做數(shù)據(jù)轉(zhuǎn)換的工作温自,然后提供給 View 展示。

這對(duì)于很簡(jiǎn)單的 Model 和 簡(jiǎn)單的 View 是沒有問題的拇涤,如果捣作,Model 很復(fù)雜(字段很多,而且不能直接使用)鹅士,那么 Presenter 的任務(wù)就會(huì)很重券躁。

這里就不做演示了,很多人應(yīng)該都在這么用掉盅,或者曾經(jīng)是這么用的也拜。

使用 VO 的好處

上面說了一堆 VO 的實(shí)現(xiàn)方式,但是就是沒提使用 VO 到底有何益處趾痘;或者說 VO 存在的意義慢哈。下面就我個(gè)人的理解,談?wù)勎艺J(rèn)為 VO 的好處永票。

統(tǒng)一命名習(xí)慣

很多時(shí)候數(shù)據(jù)來源是網(wǎng)絡(luò)(服務(wù)器端)卵贱,那么這就可能有一個(gè)問題。服務(wù)器端的命名習(xí)慣可能與客戶端有很大區(qū)別侣集,還有不同服務(wù)器端開發(fā)的命名習(xí)慣也可能不同(如:使用 PHP 開發(fā)的服務(wù)器程序和使用 Java 開發(fā)的服務(wù)器程序命名很可能就是不同的)键俱。

簡(jiǎn)而言之,那就是服務(wù)器反給我們的字段和我們項(xiàng)目中的命名習(xí)慣不同世分,很多人說编振,這沒辦法啊臭埋!總不能讓服務(wù)器改吧踪央!

是的,客戶端和服務(wù)器端的命名很難統(tǒng)一瓢阴,有人會(huì)說畅蹂,不統(tǒng)一就不統(tǒng)一,又不影響使用荣恐。確實(shí)魁莉,不影響使用,但是,我們追求完美不是(先從最基本的命名規(guī)范做起旗唁,哈哈)。

所以痹束,從這個(gè)角度來考慮检疫,我建議原始的 Model 那就按照接口文檔來(當(dāng)然,如果使用 Gson 的話祷嘶,關(guān)于命名不統(tǒng)一還是可以解決的屎媳,有興趣的可以自行 Google)。我們自己針對(duì) View 定義一套 VO论巍,這個(gè)可以完全按照我們自己的命名規(guī)范來烛谊,至少這里是統(tǒng)一的。

解耦 View 和 Model

解耦嘉汰,這個(gè)就不用多說了吧丹禀!我都不直接使用你了,這還不是解耦鞋怀,View 依賴的是 VO双泪,而不再依賴原始的 Modle。關(guān)于解耦所帶來的優(yōu)點(diǎn)密似,這里就不詳述了焙矛,一搜一堆...

鋪平數(shù)據(jù)結(jié)構(gòu)

鋪平數(shù)據(jù)結(jié)構(gòu)” —— 可以理解為將原來有多級(jí)(層級(jí)較深)的對(duì)象,轉(zhuǎn)換為層級(jí)較淺的對(duì)象残腌。

我曾在項(xiàng)目中遇到這樣一個(gè)問題:有很多相似的頁面村斟,但是服務(wù)器端給的字段都是不同的,這就需要建立多個(gè) Model 來解析服務(wù)器給的數(shù)據(jù)抛猫◇№铮考慮到頁面基本一樣,那就不需要提供多個(gè)頁面了邑滨,直接用一個(gè)頁面日缨,往里面填充不同的數(shù)據(jù)就可以了。那么掖看,問題來了匣距,這會(huì)導(dǎo)致要寫很多重復(fù)的填充 View 的代碼,因?yàn)?Model 是不同的哎壳。

對(duì)于上述的問題毅待,我的解決方案是,將頁面中要使用的數(shù)據(jù)抽取為獨(dú)立的 VO归榕,該頁面只需要從 VO 中獲取數(shù)據(jù)即可尸红。再就是,關(guān)于如何建立 Modle 去解析服務(wù)器數(shù)據(jù)的問題。這里外里,我只建了一個(gè) Modle怎爵,將所有使用這個(gè)頁面的接口中的返回字段都封裝到一個(gè) Modle 中。這得益于 Model 中多了字段盅蝗,并不會(huì)影響 JSON 字符串到對(duì)象的轉(zhuǎn)換(至少 Gson 是這樣的)鳖链。

上面說的這個(gè)例子,也可以認(rèn)為是“鋪平了數(shù)據(jù)結(jié)構(gòu)”墩莫。

在這個(gè)示例 Demo 中芙委,可以很好的演示 —— “鋪平數(shù)據(jù)結(jié)構(gòu)” 。
先看下原始的 JSON 字符串:

{
    "status": "ok",
    "now": {
        "cond": {
            "code": "100",
            "txt": "晴"
        },
        "fl": "30",
        "hum": "20%",
        "pcpn": "0.0",
        "pres": "1001",
        "tmp": "32",
        "vis": "10",
        "wind": {
            "deg": "10", //風(fēng)向(360度)
            "dir": "北風(fēng)", //風(fēng)向
            "sc": "3級(jí)", //風(fēng)力
            "spd": "15" //風(fēng)速(kmph)
        }
    }
}

看一下狂秦,上面的 JSON 字符串灌侣,如果需要獲取風(fēng)速,那么需要先訪問 now裂问,在訪問 wind侧啼,然后才能獲取到 spd 字段。在代碼中就如下:

weatherBean.getNow().getWind().getSpd();

而在我們的 VO 中愕秫,可以直接取到:

// 數(shù)據(jù)已經(jīng)轉(zhuǎn)換過了慨菱,這里直接可以取到
public String getWindSpeed() {
    return windSpeed;
}

減少可能的問題

其實(shí),View 和 Model 的耦合就是一個(gè)很大的問題戴甩,哈哈符喝,這個(gè)確實(shí)能解決。

還有一些問題可以得到避免甜孤,例如协饲,減少 View 中對(duì) Model 的取值的各種判斷(當(dāng)然 MVP 就能解決),避免 Model 中的數(shù)據(jù)異常導(dǎo)致 View 崩潰缴川。

這里就不多說這個(gè)問題了茉稠,等你遇到的時(shí)候,自然就知道能夠避免哪些問題了。(偷個(gè)懶,這個(gè)以后有機(jī)會(huì)再豐富吧)

VO 使用演示

直接看圖吧氛驮,就不上 GIF 了。

VO Class 演示
VO Interface 演示
VO Method 演示

小結(jié)

沒有可以解決一切問題的妙藥膀篮,no magic。

關(guān)于上述 VO 的各種形式岂膳,需要根據(jù)具體的場(chǎng)景(項(xiàng)目)來區(qū)分誓竿,當(dāng)然這也在很大程度上取決于個(gè)人的習(xí)慣以及項(xiàng)目的大小。

如果是比較大的項(xiàng)目谈截,那么建議直接抽出一個(gè) VO 包來筷屡,為每個(gè) View 都提供單獨(dú)的 VO 對(duì)象涧偷,這樣也可以保證項(xiàng)目的統(tǒng)一性,不會(huì)破壞層之間的依賴毙死。

如果是小項(xiàng)目燎潮,那么可以混著用,覺得哪種方式使用起來最方便扼倘,那就使用哪種吧跟啤!

還是那句話,沒有一定之規(guī)唉锌,要依據(jù)使用場(chǎng)景來確定。

項(xiàng)目地址:
GitHub

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末竿奏,一起剝皮案震驚了整個(gè)濱河市袄简,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泛啸,老刑警劉巖绿语,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異候址,居然都是意外死亡吕粹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門岗仑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來匹耕,“玉大人,你說我怎么就攤上這事荠雕∥绕洌” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵炸卑,是天一觀的道長既鞠。 經(jīng)常有香客問我,道長盖文,這世上最難降的妖魔是什么嘱蛋? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮五续,結(jié)果婚禮上洒敏,老公的妹妹穿的比我還像新娘。我一直安慰自己返帕,他們只是感情好桐玻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著荆萤,像睡著了一般镊靴。 火紅的嫁衣襯著肌膚如雪铣卡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天偏竟,我揣著相機(jī)與錄音煮落,去河邊找鬼。 笑死踊谋,一個(gè)胖子當(dāng)著我的面吹牛蝉仇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播殖蚕,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼轿衔,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了睦疫?” 一聲冷哼從身側(cè)響起害驹,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛤育,沒想到半個(gè)月后宛官,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瓦糕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年底洗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咕娄。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡亥揖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谭胚,到底是詐尸還是另有隱情徐块,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布灾而,位于F島的核電站胡控,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏旁趟。R本人自食惡果不足惜昼激,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望锡搜。 院中可真熱鬧橙困,春花似錦、人聲如沸耕餐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肠缔。三九已至夏跷,卻和暖如春哼转,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背槽华。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國打工壹蔓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人猫态。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓佣蓉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親亲雪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子勇凭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

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

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件义辕、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,094評(píng)論 4 62
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理套像,服務(wù)發(fā)現(xiàn),斷路器终息,智...
    卡卡羅2017閱讀 134,652評(píng)論 18 139
  • iOS網(wǎng)絡(luò)架構(gòu)討論梳理整理中。贞让。周崭。 其實(shí)如果沒有APIManager這一層是沒法使用delegate的,畢竟多個(gè)單...
    yhtang閱讀 5,188評(píng)論 1 23
  • D君說“人做到兩點(diǎn)就可以保證心理健康:接受現(xiàn)實(shí),與時(shí)俱進(jìn)销部∶剑” 我做到了嗎?好像沒有…這是讓人有些感傷難捱的舅桩。 確實(shí)...
    靈子94閱讀 265評(píng)論 7 0
  • 今天孩子被點(diǎn)名作業(yè)沒有寫完酱虎,原因是沒有發(fā)給他卷子,我想了想擂涛,孩子二年級(jí)了读串,我還沒有數(shù)學(xué)老師的電話,我不能和老師...
    67fbaec5208f閱讀 284評(píng)論 0 0