RxJava 沉思錄(四):總結(jié)

本文是 "RxJava 沉思錄" 系列的最后一篇分享占哟。本系列所有分享:

我們在本系列開篇中解虱,曾經(jīng)留了一個問題:RxJava 是否可以讓我們的代碼更簡潔?作為本系列的最后一篇分享,我們將詳細(xì)地探討這個問題射窒。承接前面兩篇 “時間維度” 和 “空間維度” 的探討,我們首先從 RxJava 的維度 開始說起。

RxJava 的維度

在前面兩篇分享中冶忱,我們解讀了很多案例,最終得出結(jié)論:RxJava 通過 Observable 這個統(tǒng)一的接口境析,對其相關(guān)的事件囚枪,在空間維度和事件維度進(jìn)行重新組織派诬,來簡化我們?nèi)粘5氖录?qū)動編程

前文中提到:

有了 Observable 以后的 RxJava 才剛剛插上了想象力的翅膀链沼。

RxJava 所有想象力的基石和源泉在于 Observable 這個統(tǒng)一的接口默赂,有了它,配合我們各種各樣的操作符括勺,才可以在時間空間維度玩出花樣缆八。

我們回想一下原先我們基于 Callback 的編程范式:

btn.setOnClickListener(v -> {
    // handle click event
})

在基于 Callback 的編程范式中,我們的 Callback沒有維度 的疾捍。它只能夠 響應(yīng)孤立的事件奈辰,即來一個事件,我處理一個事件乱豆。假設(shè)同一個事件前后存在依賴關(guān)系奖恰,或者不同事件之間存在依賴關(guān)系,無論是時間維度還是空間維度宛裕,如果我們還是繼續(xù)用 Callback 的方式處理瑟啃,我們必然需要新增許多額外的數(shù)據(jù)結(jié)構(gòu)來保存中間的上下文信息,同時 Callback 本身的邏輯也需要修改揩尸,觀察者的邏輯會變得不那么純粹蛹屿。

但是 RxJava 給我們的事件驅(qū)動型編程帶來了新的思路,RxJava 的 Observable 一下子把我們的維度拓展到了時間和空間兩個維度岩榆。如果事件與事件間存在依賴關(guān)系蜡峰,原先我們需要新增的數(shù)據(jù)結(jié)構(gòu)以及在 Callback 內(nèi)寫的額外的控制邏輯的代碼,現(xiàn)在都可以不用寫朗恳,我們只需要利用 Observable 的操作符對事件在時間和空間維度進(jìn)行重新組織,就可以實現(xiàn)一樣的效果载绿,而觀察者的邏輯幾乎不需要修改粥诫。

所以如果把 RxJava 的編程思想和傳統(tǒng)的面向 Callback 的編程思想進(jìn)行對比,用一個詞形容的話崭庸,那就是 降維打擊怀浆。

這是我認(rèn)為目前大多數(shù)與 RxJava 有關(guān)的技術(shù)分享沒有提到的一個非常重要的點,并且我認(rèn)為這才是 RxJava 最精髓最核心的思想怕享。RxJava 對我們?nèi)粘>幊套钪匾呢暙I(xiàn)执赡,就是提升了我們原先對于事件驅(qū)動型編程的思考的維度,給人一種大夢初醒的感覺函筋,和這點比起來沙合,所謂的 “鏈?zhǔn)綄懛ā?這種語法糖什么的,根本不值一提跌帐。

生產(chǎn)者消費(fèi)者模式中 RxJava 扮演的角色

無論是同步還是異步首懈,我們?nèi)粘5氖录?qū)動型編程可以被看成是一種 “生產(chǎn)者——消費(fèi)者” 模型:

Callback

在異步的情況下绊率,我們的代碼可以被分為兩大塊,一塊生產(chǎn)事件究履,一塊消費(fèi)事件滤否,兩者通過 Callback 聯(lián)系起來。而 Callback 是輕量級的最仑,大多數(shù)和 Callback 相關(guān)的邏輯就僅僅是設(shè)置回調(diào)和取消設(shè)置的回調(diào)而已藐俺。

如果我們的項目中引入了 RxJava ,我們可以發(fā)現(xiàn)泥彤,“生產(chǎn)者——消費(fèi)者” 這個模型中欲芹,中間多了一層 RxJava 相關(guān)的邏輯層:

RxJava

而這一層的作用,我們在之前的討論中已經(jīng)明確全景,是用來對生產(chǎn)者產(chǎn)生的事件進(jìn)行重新組織的耀石。這個架構(gòu)之下,生產(chǎn)者這一層的變化不會很大爸黄,直接受影響的是消費(fèi)者這一層滞伟,由于 RxJava 這一層對事件進(jìn)行了“預(yù)處理”,消費(fèi)者這一層代碼會比之前輕很多炕贵。同時由于 RxJava 取代了原先的 Callback 這一層梆奈,RxJava 這一層的代碼是會比原先 Callback 這一層更厚。

這么做還會有什么其他的好處呢称开?首先最直接的好處便是代碼會更易于測試亩钟。原先生產(chǎn)者和消費(fèi)者之間是耦合的,由于現(xiàn)在引入了 RxJava鳖轰,生產(chǎn)者和消費(fèi)者之間沒有直接的耦合關(guān)系清酥,測試的時候可以很方便的對生產(chǎn)者和消費(fèi)者分開進(jìn)行測試。比如原先網(wǎng)絡(luò)請求相關(guān)邏輯蕴侣,測試就不是很方便焰轻,但是如果我們使用 RxJava 進(jìn)行解耦以后,觀察者僅僅只是耦合 Observable 這個接口而已昆雀,我們可以自己手動創(chuàng)建用于測試的 Observable辱志,這些 Observable 負(fù)責(zé)發(fā)射 Mock 的數(shù)據(jù),這樣就可以很方便的對觀察者的代碼進(jìn)行測試狞膘,而不需要真正的去發(fā)起網(wǎng)絡(luò)請求揩懒。

取消訂閱與 Scheduler

取消訂閱這個功能也是我們在觀察者模式中經(jīng)常用到的一個功能點,尤其是在 Android 開發(fā)領(lǐng)域挽封,由于 Activity 生命周期的關(guān)系已球,我們經(jīng)常需要將網(wǎng)絡(luò)請求與 Activity 生命周期綁定,即在 Activity 銷毀的時候取消所有未完成的網(wǎng)絡(luò)請求。

常規(guī)面向 Callback 的編程方式我們無法在觀察者這一層完成取消訂閱這一邏輯和悦,我們常常需要找到事件生產(chǎn)者這一層才能完成取消訂閱退疫。例如我們需要取消點擊事件的訂閱時,我們不得不找到點擊事件產(chǎn)生的源頭鸽素,來取消訂閱:

btn.setOnClickListener(null);

然而在 RxJava 的世界里褒繁,取消訂閱這個邏輯終于下放到觀察者這一層了。事件的生產(chǎn)者需要在提供 Observable 的同時馍忽,實現(xiàn)當(dāng)它的觀察者取消訂閱時棒坏,它應(yīng)該實現(xiàn)的邏輯(例如釋放資源);事件的觀察者當(dāng)訂閱一個 Observable 時遭笋,它同時會得到一個 Disposable 坝冕,觀察者希望取消訂閱事件的時候,只需要通過這個接口通知事件生產(chǎn)者即可瓦呼,完全不需要了解事件是如何產(chǎn)生的喂窟、事件的源頭在哪里。

至此央串,生產(chǎn)者和消費(fèi)者在 RxJava 的世界里已經(jīng)完成了徹底的解耦磨澡。除此以外,RxJava 還提供了好用的線程池质和,在 生產(chǎn)者——消費(fèi)者 這個模型里稳摄,我們常常會要求兩者工作在不同的線程中,切換線程是剛需饲宿,RxJava 完全考慮到了這一點厦酬,并且把切換線程的功能封裝成了 subscribeOnobserverOn 兩個操作符,我們可以在事件流處理的任何時機(jī)隨意切換線程瘫想,鑒于這一塊已經(jīng)有很多資料了仗阅,這里不再詳細(xì)展開。

面向 Observable 的 AOP:compose 操作符

這一塊不屬于 RxJava 的核心 Feature国夜,但是如果掌握好這塊减噪,可以讓我們使用 RxJava 編程效率大大提升。

我們舉一個實際的例子支竹,Activity 內(nèi)發(fā)起的網(wǎng)絡(luò)請求都需要綁定生命周期,即我們需要在 Activity 銷毀的時候取消訂閱所有未完成的網(wǎng)絡(luò)請求鸠按。假設(shè)我目前已經(jīng)可以獲得一個 Observable<ActivityEvent>, 這是一個能接收到 Activity 生命周期的 Observable(獲取方法可以借鑒三方框架 RxLifecycle礼搁,或者自己內(nèi)建一個不可見 Fragment,用來接收生命周期的回調(diào))目尖。

那么用來保證每一個網(wǎng)絡(luò)請求都能綁定 Activity 生命周期的代碼應(yīng)如下所示:

public interface NetworkApi {
    @GET("/path/to/api")
    Call<List<Photo>> getAllPhotos();
}

public class MainActivity extends Activity {

    Observable<ActivityEvent> lifecycle = ...
    NetworkApi networkApi = ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // 發(fā)起請求同時綁定生命周期
        networkApi.getAllPhotos()
            .compose(bindToLifecycle())
            .subscribe(result -> {
                // handle results
            });
    }

    private <T> ObservableTransformer<T, T> bindToLifecycle() {
        return upstream -> upstream.takeUntil(
            lifecycle.filter(ActivityEvent.DESTROY::equals)
        );
    }
}

如果您之前沒有接觸過 ObservableTransformer, 這里做一個簡單介紹馒吴,它通常和 compose 操作符一起使用,用來把一個 Observable 進(jìn)行加工、修飾饮戳,甚至替換為另一個 Observable豪治。

在這里我們封裝了一個 bindToLifecycle 方法,它的返回類型是 ObservableTransformer扯罐,在 ObservableTransformer 內(nèi)部负拟,我們修飾了原 Observable, 使其可以在接收到 Activity 的 DESTROY 事件的時候自動取消訂閱,這個邏輯是由 takeUntil 這個操作符完成的歹河。其實我們可以把這個 bindToLifecycle 方法抽取出來掩浙,放到公共的工具類,這樣任何的 Activity 內(nèi)部發(fā)起的網(wǎng)絡(luò)請求時秸歧,都只需要加一行 .compose(bindToLifecycle()) 就可以保證綁定生命周期了厨姚,從此再也不必?fù)?dān)心由于網(wǎng)絡(luò)請求引起的內(nèi)存泄漏和崩潰了。

事實上我們還可以有更多玩法键菱, 上面 ObservableTransformer 內(nèi)部的 upstream 對象谬墙,就是一個 Observable,也就是說可以調(diào)用它的 doOnSubscribedoOnTerminate 方法经备,我們可以在這兩個方法里實現(xiàn) Loading 動畫的顯隱:

private <T> ObservableTransformer<T, T> applyLoading() {
    return upstream -> upstream
        .doOnSubscribe(() -> {
            loading.show();
        })
        .doOnTerminae(() -> {
            loading.dismiss();
        });    
    );
}

這樣拭抬,我們的網(wǎng)絡(luò)請求只要調(diào)用兩個 compose 操作符,就可以完成生命周期的綁定以及與之對應(yīng)的 Loading 動畫的顯隱了:

networkApi.getAllPhotos()
    .compose(bindToLifecycle())
    .compose(applyLoading())
    .subscribe(result -> {
        // handle results
    });

操作符 compose 是 RxJava 給我們提供的可以面向 Observable 進(jìn)行 AOP 的接口弄喘,善加利用就可以幫我們節(jié)省大量的時間和精力玖喘。

RxJava 真的讓你的代碼更簡潔?

在前文中蘑志,我們還留了一個問題尚未解答:RxJava 真的更簡潔嗎累奈?本文中列舉了很多實際的例子,我們也看到了急但,從代碼量看澎媒,有時候使用 RxJava 的版本比 Callback 的版本更少,有時候兩者差不多波桩,有時候 Callback 版本的代碼反而更少戒努。所以我們可能無法從代碼量上對兩者做出公正的考量,所以我們需要從其他方面镐躲,例如代碼的閱讀難度储玫、可維護(hù)性上去評判了。

首先我想要明確一點萤皂,RxJava 是一個 “夾帶了私貨” 的框架撒穷,它本身最重要的貢獻(xiàn)是提升了我們思考事件驅(qū)動型編程的維度,但是它與此同時又逼迫我們?nèi)ソ邮芰撕瘮?shù)式編程裆熙。函數(shù)式編程在處理集合端礼、列表這些數(shù)據(jù)結(jié)構(gòu)時相比較指令式編程具有先天的優(yōu)勢禽笑,我理解框架的設(shè)計者,由于框架本身提升了我們對事件思考的維度蛤奥,那么無論是時間維度還是空間維度佳镜,一連串發(fā)射出來的事件其實就可以被看成許許多多事件的集合,既然是集合凡桥,那肯定是使用函數(shù)式的風(fēng)格去處理更加優(yōu)雅蟀伸。

image

原先的時候,我們接觸的函數(shù)式編程只是用于處理靜態(tài)的數(shù)據(jù)唬血,當(dāng)我們接觸了 RxJava 之后望蜡,發(fā)現(xiàn)動態(tài)的異步事件組成的集合居然也可以使用函數(shù)式編程的方式去處理,我不由地佩服框架設(shè)計者的腦洞大開拷恨。事實上脖律,RxJava 很多操作符都是直接照搬函數(shù)式編程中處理集合的函數(shù),例如:map, filter, flatMap, reduce 等等腕侄。

但是小泉,函數(shù)式編程是一把雙刃劍,它也會給你帶來不利的因素冕杠,一方面微姊,這意味著你的團(tuán)隊都需要了解函數(shù)式編程的思想,另一方面分预,函數(shù)式的編程風(fēng)格兢交,意味著代碼會比原先更加抽象。

比如在前面的分享中 “實現(xiàn)一個具有多種類型的 RecyclerView” 這個例子中笼痹, combineLatest 這個操作符配喳,完成了原先 onOk() 方法、resultTypes凳干、responseList 一起配合才完成的任務(wù)晴裹。雖然原先的版本代碼不夠內(nèi)聚,不如 RxJava 版本的簡練救赐,但是如果從可閱讀性和可維護(hù)性上來看涧团,我認(rèn)為原先的版本更好,因為我看到這幾個方法和字段经磅,可以推測出這段代碼的意圖是什么泌绣,可是如果是 combineLatest 這個操作符,也許我寫的那個時候我知道我是什么意圖预厌,一旦過一段時間回來看阿迈,我對著這個這個 combineLatest 操作符可能就一臉懵逼了,我必須從這個事件流最開始的地方從上往下捋一遍配乓,結(jié)合實際的業(yè)務(wù)邏輯仿滔,我才能回想起為什么當(dāng)時要用 combineLatest 這個操作符了。

再舉一個例子犹芹,在 “社交軟件上消息的點贊與取消點贊” 這個例子中崎页,如果我不是對這種“把事件流中相鄰事件進(jìn)行比較”的編碼方式了如指掌的話,一旦隔一段時間腰埂,我再次面對這幾個 debounce 飒焦、zipWithflatMap 操作符時屿笼,我可能會懷疑自己寫的代碼牺荠。自己寫的代碼都如此,更何況大多數(shù)情況下我們需要面對別人寫的代碼驴一。

這就是為什么 RxJava 寫出的代碼會更加抽象休雌,因為 RxJava 的操作符是我們平時處理業(yè)務(wù)邏輯時常用方法的高度抽象combineLatest 是對我們自己寫的 onOk 等方法的抽象肝断,zipWith 幫我們省略了本來要寫的中間變量杈曲,debounce 操作符替代了我們本來要寫的計時器邏輯。從功能上來講兩者其實是等價的胸懈,只不過 RxJava 給我們提供了高度抽象凝練担扑,更加具有普適性的寫法。

在本文前半部分趣钱,我們說到過涌献,有的人認(rèn)為 RxJava 是簡潔的,而有的人的看法則完全相反首有,這件事的本質(zhì)在于大家對 簡潔 的期望不同燕垃,大多數(shù)人認(rèn)為的簡潔指得是代碼簡單好理解,而高度抽象的代碼是不滿足這一點的绞灼,所以很多人最后發(fā)現(xiàn)理解抽象的 RxJava 代碼需要花更多的時間利术,反而不 “簡潔” 。認(rèn)為 RxJava 簡潔的人所認(rèn)為的 簡潔 更像是那種類似數(shù)學(xué)概念上的那種 簡潔低矮,這是因為函數(shù)式編程的抽象風(fēng)格與數(shù)學(xué)更接近印叁。我們舉個例子,大家都知道牛頓第二定律军掂,可是你知道牛頓在《自然哲學(xué)的數(shù)學(xué)原理》上發(fā)表牛頓二定律的時候的原始公式表示是什么樣的嗎:

Newton's second law

公式中的 p 表示動量轮蜕,這是牛頓所認(rèn)為的"簡潔",而我們大多數(shù)人認(rèn)為簡單好記的版本是 “物體的加速度等于施加在物體上的力除以物體的質(zhì)量”蝗锥。

這就是為什么跃洛,我在前面提前下了那個結(jié)論:對于大多數(shù)人,RxJava 不等于簡潔终议,有時候甚至是更難以理解的代碼以及更低的項目可維護(hù)性汇竭。

而目前大多數(shù)我看到的有關(guān) RxJava 的技術(shù)文章舉例說明的所謂 “邏輯簡潔” 或者是 “隨著程序邏輯的復(fù)雜性提高葱蝗,依然能夠保持簡潔” 的例子大多數(shù)都是不恰當(dāng)?shù)摹R环矫嫠麄儍H僅停留在 Callback 的維度细燎,舉那種依次執(zhí)行的異步任務(wù)的例子两曼,完全沒有點到 RxJava 對處理問題的維度的提升這一點;二是舉的那些例子實在難以令人信服玻驻,至少我并沒有覺得那些例子用了 RxJava 相比 Callback 有多么大的提升悼凑。

RxJava 是否適合你的項目

綜上所述,我們可以得出這樣的結(jié)論璧瞬,RxJava 是一個思想優(yōu)秀的框架户辫,而且是那種在工程領(lǐng)域少見的帶有學(xué)院派氣息和理想主義色彩的框架,他是一種新型的事件驅(qū)動型編程范式嗤锉。 RxJava 最重要的貢獻(xiàn)渔欢,就是提升了我們原先對于事件驅(qū)動型編程的思考的維度,允許我們可以從時間和空間兩個維度去重新組織事件瘟忱。

此外膘茎,RxJava 好在哪,真的和“觀察者模式”酷誓、“鏈?zhǔn)骄幊獭迸怠ⅰ熬€程池”、“解決 Callback Hell”等等關(guān)系沒那么大盐数,這些特性相比上面總結(jié)的而言棒拂,都是微不足道的。

我是不會用“簡潔”玫氢、“邏輯簡潔”帚屉、“清晰”、“優(yōu)雅” 那樣空洞的字眼去描述 RxJava 這個框架的漾峡,這確實是一個學(xué)習(xí)曲線陡峭的框架攻旦,而且如果團(tuán)隊成員整體對函數(shù)式編程認(rèn)識不夠深刻的話,項目的后期維護(hù)也是充滿風(fēng)險的生逸。

當(dāng)然我希望你也不要因此被我嚇到牢屋,我個人是推崇 RxJava 的,在我本人參與的項目中已經(jīng)大規(guī)模鋪開使用了 RxJava槽袄。本文前面提到過:

RxJava 是一種新的 事件驅(qū)動型 編程范式烙无,它以異步為切入點,試圖一統(tǒng) 同步異步 的世界遍尺。

在我參與的項目中截酷,我已經(jīng)漸漸能感受到這種 “天下大同” 的感覺了。這也是為什么我能聽到很多人都會說 “一旦用了 RxJava 就很難再放棄了”乾戏。

也許這時候你會問我迂苛,到底推不推薦大家使用 RxJava 三热?我認(rèn)為是這樣,如果你認(rèn)為在你的項目里三幻,Callback 模式已經(jīng)不能滿足你的日常需要康铭,事件之間存在復(fù)雜的依賴關(guān)系,你需要從更高的維度空間去重新思考你的問題赌髓,或者說你需要經(jīng)常在時間或者空間維度上去重新組織你的事件,那么恭喜你催跪, RxJava 正是為你打造的锁蠕;如果你認(rèn)為在你的項目里,目前使用 Callback 模式已經(jīng)很好滿足了你的日常開發(fā)需要懊蒸,簡單的業(yè)務(wù)邏輯也根本玩不出什么新花樣荣倾,那么 RxJava 就是不適合你的。

(完)

本文屬于 "RxJava 沉思錄" 系列骑丸,歡迎閱讀本系列的其他分享:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市通危,隨后出現(xiàn)的幾起案子铸豁,更是在濱河造成了極大的恐慌,老刑警劉巖菊碟,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件节芥,死亡現(xiàn)場離奇詭異,居然都是意外死亡逆害,警方通過查閱死者的電腦和手機(jī)头镊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來魄幕,“玉大人相艇,你說我怎么就攤上這事〈吭桑” “怎么了坛芽?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長翼抠。 經(jīng)常有香客問我靡馁,道長,這世上最難降的妖魔是什么机久? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任臭墨,我火速辦了婚禮,結(jié)果婚禮上膘盖,老公的妹妹穿的比我還像新娘胧弛。我一直安慰自己尤误,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布结缚。 她就那樣靜靜地躺著损晤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪红竭。 梳的紋絲不亂的頭發(fā)上尤勋,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天,我揣著相機(jī)與錄音茵宪,去河邊找鬼最冰。 笑死,一個胖子當(dāng)著我的面吹牛稀火,可吹牛的內(nèi)容都是我干的暖哨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼凰狞,長吁一口氣:“原來是場噩夢啊……” “哼篇裁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起赡若,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤达布,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后逾冬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體往枣,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年粉渠,在試婚紗的時候發(fā)現(xiàn)自己被綠了分冈。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡霸株,死狀恐怖雕沉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情去件,我是刑警寧澤坡椒,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站尤溜,受9級特大地震影響倔叼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宫莱,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一丈攒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦巡验、人聲如沸际插。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽框弛。三九已至,卻和暖如春捕捂,著一層夾襖步出監(jiān)牢的瞬間瑟枫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工指攒, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留慷妙,地道東北人。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓幽七,卻偏偏與公主長得像,于是被迫代替她去往敵國和親溅呢。 傳聞我的和親對象是個殘疾皇子澡屡,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,507評論 2 359

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