基于某某某的組件抽離

組件化方案(傳送門:blog.cnbang.net/tech/3080/)

首先我覺(jué)得”組件”在這里不太合適丁恭,因?yàn)榘次依斫饨M件是指比較小的功能塊臀晃,這些組件不需要多少組件間通信誉结,沒(méi)什么依賴,也就不需要做什么其他處理,面向?qū)ο缶湍芨愣ㄡ场6@里提到的是較大粒度的業(yè)務(wù)功能,我們習(xí)慣稱為”模塊”鞭莽。為了方便表述坊秸,下面模塊和組件代表同一個(gè)意思,都是指較大粒度的業(yè)務(wù)模塊澎怒。

一個(gè) APP 有多個(gè)模塊褒搔,模塊之間會(huì)通信,互相調(diào)用喷面,例如微信讀書有 書籍詳情 想法列表 閱讀器 發(fā)現(xiàn)卡片 等等模塊星瘾,這些模塊會(huì)互相調(diào)用,例如 書籍詳情要調(diào)起閱讀器和想法列表惧辈,閱讀器要調(diào)起想法列表和書籍詳情琳状,等等,一般我們是怎樣調(diào)用呢咬像,以閱讀器為例算撮,會(huì)這樣寫:

#import "WRBookDetailViewController.h"

#import "WRReviewViewController.h"

@implementation WRReadingViewController

- (void)gotoDetail {

WRBookDetailViewController *detailVC = [[WRBookDetailViewController alloc] initWithBookId:self.bookId];

[self.navigationController pushViewController:detailVC animated:YES];

}

- (void)gotoReview {

WRReviewViewController *reviewVC = [[WRReviewViewController alloc] initWithBookId:self.bookId reviewType:1];

[self.navigationController pushViewController:reviewVC animated:YES];

}

@end

看起來(lái)挺好,這樣做簡(jiǎn)單明了县昂,沒(méi)有多余的東西肮柜,項(xiàng)目初期推薦這樣快速開(kāi)發(fā),但到了項(xiàng)目越來(lái)越龐大倒彰,這種方式會(huì)有什么問(wèn)題呢审洞?顯而易見(jiàn),每個(gè)模塊都離不開(kāi)其他模塊待讳,互相依賴粘在一起成為一坨:

這樣揉成一坨對(duì)測(cè)試/編譯/開(kāi)發(fā)效率/后續(xù)擴(kuò)展都有一些壞處芒澜,那怎么解開(kāi)這一坨呢。很簡(jiǎn)單创淡,按軟件工程的思路痴晦,下意識(shí)就會(huì)加一個(gè)中間層:

叫他 Mediator Manager Router 什么都行,反正就是負(fù)責(zé)轉(zhuǎn)發(fā)信息的中間層琳彩,暫且叫他 Mediator誊酌。

看起來(lái)順眼多了,但這里有幾個(gè)問(wèn)題:

Mediator 怎么去轉(zhuǎn)發(fā)組件間調(diào)用露乏?

一個(gè)模塊只跟 Mediator 通信碧浊,怎么知道另一個(gè)模塊提供了什么接口?

按上圖的畫法瘟仿,模塊和 Mediator 間互相依賴箱锐,怎樣破除這個(gè)依賴?

方案1

對(duì)于前兩個(gè)問(wèn)題劳较,最直接的反應(yīng)就是在 Mediator 直接提供接口驹止,調(diào)用對(duì)應(yīng)模塊的方法:

//Mediator.m

#import "BookDetailComponent.h"

#import "ReviewComponent.h"

@implementation Mediator

+ (UIViewController *)BookDetailComponent_viewController:(NSString *)bookId {

return [BookDetailComponent detailViewController:bookId];

}

+ (UIViewController *)ReviewComponent_viewController:(NSString *)bookId reviewType:(NSInteger)type {

return [ReviewComponent reviewViewController:bookId type:type];

}

@end

//BookDetailComponent 組件

#import "Mediator.h"

#import "WRBookDetailViewController.h"

@implementation BookDetailComponent

+ (UIViewController *)detailViewController:(NSString *)bookId {

WRBookDetailViewController *detailVC = [[WRBookDetailViewController alloc] initWithBookId:bookId];

return detailVC;

}

@end

//ReviewComponent 組件

#import "Mediator.h"

#import "WRReviewViewController.h"

@implementation ReviewComponent

+ (UIViewController *)reviewViewController:(NSString *)bookId type:(NSInteger)type {

UIViewController *reviewVC = [[WRReviewViewController alloc] initWithBookId:bookId type:type];

return reviewVC;

}

@end

然后在閱讀模塊里:

//WRReadingViewController.m

#import "Mediator.h"

@implementation WRReadingViewController

- (void)gotoDetail:(NSString *)bookId {

UIViewController *detailVC = [Mediator BookDetailComponent_viewControllerForDetail:bookId];

[self.navigationController pushViewController:detailVC];

UIViewController *reviewVC = [Mediator ReviewComponent_viewController:bookId type:1];

[self.navigationController pushViewController:reviewVC];

}

@end

這就是一開(kāi)始架構(gòu)圖的實(shí)現(xiàn)浩聋,看起來(lái)顯然這樣做并沒(méi)有什么好處,依賴關(guān)系并沒(méi)有解除幢哨,Mediator 依賴了所有模塊赡勘,而調(diào)用者又依賴 Mediator嫂便,最后還是一坨互相依賴捞镰,跟原來(lái)沒(méi)有 Mediator 的方案相比除了更麻煩點(diǎn)其他沒(méi)區(qū)別。

那怎么辦呢毙替。

怎樣讓Mediator解除對(duì)各個(gè)組件的依賴岸售,同時(shí)又能調(diào)到各個(gè)組件暴露出來(lái)的方法?對(duì)于OC有一個(gè)法寶可以做到厂画,就是runtime反射調(diào)用:

//Mediator.m

@implementation Mediator

+ (UIViewController *)BookDetailComponent_viewController:(NSString *)bookId {

Class cls = NSClassFromString(@"BookDetailComponent");

return [cls performSelector:NSSelectorFromString(@"detailViewController:") withObject:@{@"bookId":bookId}];

}

+ (UIViewController *)ReviewComponent_viewController:(NSString *)bookId type:(NSInteger)type {

Class cls = NSClassFromString(@"ReviewComponent");

return [cls performSelector:NSSelectorFromString(@"reviewViewController:") withObject:@{@"bookId":bookId, @"type": @(type)}];

}

@end

這下 Mediator 沒(méi)有再對(duì)各個(gè)組件有依賴了凸丸,你看已經(jīng)不需要 #import 什么東西了,對(duì)應(yīng)的架構(gòu)圖就變成:

只有調(diào)用其他組件接口時(shí)才需要依賴 Mediator袱院,組件開(kāi)發(fā)者不需要知道 Mediator 的存在屎慢。

等等,既然用runtime就可以解耦取消依賴忽洛,那還要Mediator做什么腻惠?組件間調(diào)用時(shí)直接用runtime接口調(diào)不就行了,這樣就可以沒(méi)有任何依賴就完成調(diào)用:

//WRReadingViewController.m

@implementation WRReadingViewController

- (void)gotoReview:(NSString *)bookId {

Class cls = NSClassFromString(@"ReviewComponent");

UIViewController *reviewVC = [cls performSelector:NSSelectorFromString(@"reviewViewController:") withObject:@{@"bookId":bookId, @"type": @(1)}];

[self.navigationController pushViewController:reviewVC];

}

@end

這樣就完全解耦了欲虚,但這樣做的問(wèn)題是:

調(diào)用者寫起來(lái)很惡心集灌,代碼提示都沒(méi)有,每次調(diào)用寫一坨复哆。

runtime方法的參數(shù)個(gè)數(shù)和類型限制欣喧,導(dǎo)致只能每個(gè)接口都統(tǒng)一傳一個(gè)NSDictionary。這個(gè)NSDictionary里的key value是什么不明確梯找,需要找個(gè)地方寫文檔說(shuō)明和查看唆阿。

編譯器層面不依賴其他組件,實(shí)際上還是依賴了锈锤,直接在這里調(diào)用驯鳖,沒(méi)有引入調(diào)用的組件時(shí)就掛了

把它移到Mediator后:

調(diào)用者寫起來(lái)不惡心,代碼提示也有了牙咏。

參數(shù)類型和個(gè)數(shù)無(wú)限制臼隔,由 Mediator 去轉(zhuǎn)就行了,組件提供的還是一個(gè)NSDictionary參數(shù)的接口妄壶,但在Mediator 里可以提供任意類型和個(gè)數(shù)的參數(shù)摔握,像上面的例子顯式要求參數(shù)NSString *bookId和NSInteger type。

Mediator可以做統(tǒng)一處理丁寄,調(diào)用某個(gè)組件方法時(shí)如果某個(gè)組件不存在氨淌,可以做相應(yīng)操作泊愧,讓調(diào)用者與組件間沒(méi)有耦合。

到這里盛正,基本上能解決我們的問(wèn)題:各組件互不依賴删咱,組件間調(diào)用只依賴中間件Mediator,Mediator不依賴其他組件豪筝。接下來(lái)就是優(yōu)化這套寫法痰滋,有兩個(gè)優(yōu)化點(diǎn):

Mediator 每一個(gè)方法里都要寫 runtime 方法,格式是確定的续崖,這是可以抽取出來(lái)的敲街。

每個(gè)組件對(duì)外方法都要在 Mediator 寫一遍,組件一多 Mediator 類的長(zhǎng)度是恐怖的严望。

優(yōu)化后就成了 casa 的方案多艇,target-action 對(duì)應(yīng)第一點(diǎn),target就是class像吻,action就是selector峻黍,通過(guò)一些規(guī)則簡(jiǎn)化動(dòng)態(tài)調(diào)用。Category 對(duì)應(yīng)第二點(diǎn)拨匆,每個(gè)組件寫一個(gè) Mediator 的 Category姆涩,讓 Mediator 不至于太長(zhǎng)。這里有個(gè)demo

總結(jié)起來(lái)就是涮雷,組件通過(guò)中間件通信阵面,中間件通過(guò) runtime 接口解耦,通過(guò) target-action 簡(jiǎn)化寫法洪鸭,通過(guò) category 感官上分離組件接口代碼样刷。

方案2

回到 Mediator 最初的三個(gè)問(wèn)題,蘑菇街用的是另一種方式解決:注冊(cè)表的方式览爵,用URL表示接口置鼻,在模塊啟動(dòng)時(shí)注冊(cè)模塊提供的接口,一個(gè)簡(jiǎn)化的實(shí)現(xiàn):

//Mediator.m 中間件

@implementation Mediator

typedef void (^componentBlock) (id param);

@property (nonatomic, storng) NSMutableDictionary *cache

- (void)registerURLPattern:(NSString *)urlPattern toHandler:(componentBlock)blk {

[cache setObject:blk forKey:urlPattern];

}

- (void)openURL:(NSString *)url withParam:(id)param {

componentBlock blk = [cache objectForKey:url];

if (blk) blk(param);

}

@end

//BookDetailComponent 組件

#import "Mediator.h"

#import "WRBookDetailViewController.h"

+ (void)initComponent {

[[Mediator sharedInstance] registerURLPattern:@"weread://bookDetail" toHandler:^(NSDictionary *param) {

WRBookDetailViewController *detailVC = [[WRBookDetailViewController alloc] initWithBookId:param[@"bookId"]];

[[UIApplication sharedApplication].keyWindow.rootViewController.navigationController pushViewController:detailVC animated:YES];

}];

}

//WRReadingViewController.m 調(diào)用者

//ReadingViewController.m

#import "Mediator.h"

+ (void)gotoDetail:(NSString *)bookId {

[[Mediator sharedInstance] openURL:@"weread://bookDetail" withParam:@{@"bookId": bookId}];

}

這樣同樣做到每個(gè)模塊間沒(méi)有依賴蜓竹,Mediator 也不依賴其他組件箕母,不過(guò)這里不一樣的一點(diǎn)是組件本身和調(diào)用者都依賴了Mediator,不過(guò)這不是重點(diǎn)俱济,架構(gòu)圖還是跟方案1一樣嘶是。

各個(gè)組件初始化時(shí)向 Mediator 注冊(cè)對(duì)外提供的接口,Mediator 通過(guò)保存在內(nèi)存的表去知道有哪些模塊哪些接口蛛碌,接口的形式是URL->block聂喇。

這里拋開(kāi)URL的遠(yuǎn)程調(diào)用和本地調(diào)用混在一起導(dǎo)致的問(wèn)題,先說(shuō)只用于本地調(diào)用的情況,對(duì)于本地調(diào)用希太,URL只是一個(gè)表示組件的key克饶,沒(méi)有其他作用,這樣做有三個(gè)問(wèn)題:

需要有個(gè)地方列出各個(gè)組件里有什么 URL 接口可供調(diào)用誊辉。蘑菇街做了個(gè)后臺(tái)專門管理矾湃。

每個(gè)組件都需要初始化,內(nèi)存里需要保存一份表堕澄,組件多了會(huì)有內(nèi)存問(wèn)題邀跃。

參數(shù)的格式不明確,是個(gè)靈活的 dictionary奈偏,也需要有個(gè)地方可以查參數(shù)格式坞嘀。

第二點(diǎn)沒(méi)法解決躯护,第一點(diǎn)和第三點(diǎn)可以跟前面那個(gè)方案一樣惊来,在 Mediator 每個(gè)組件暴露方法的轉(zhuǎn)接口,然后使用起來(lái)就跟前面那種方式一樣了棺滞。

拋開(kāi)URL不說(shuō)裁蚁,這種方案跟方案1的共同思路就是:Mediator 不能直接去調(diào)用組件的方法,因?yàn)檫@樣會(huì)產(chǎn)生依賴继准,那我就要通過(guò)其他方法去調(diào)用枉证,也就是通過(guò) 字符串->方法 的映射去調(diào)用。runtime 接口的className + selectorName -> IMP是一種移必,注冊(cè)表的key -> block是一種室谚,而前一種是 OC 自帶的特性,后一種需要內(nèi)存維持一份注冊(cè)表崔泵,這是不必要的秒赤。

現(xiàn)在說(shuō)回 URL,組件化是不應(yīng)該跟 URL 扯上關(guān)系的憎瘸,因?yàn)榻M件對(duì)外提供的接口主要是模塊間代碼層面上的調(diào)用入篮,我們先稱為本地調(diào)用,而 URL 主要用于 APP 間通信幌甘,姑且稱為遠(yuǎn)程調(diào)用潮售。按常規(guī)思路者應(yīng)該是對(duì)于遠(yuǎn)程調(diào)用,再加個(gè)中間層轉(zhuǎn)發(fā)到本地調(diào)用锅风,讓這兩者分開(kāi)酥诽。那這里這兩者混在一起有什么問(wèn)題呢?

如果是 URL 的形式皱埠,那組件對(duì)外提供接口時(shí)就要同時(shí)考慮本地調(diào)用和遠(yuǎn)程調(diào)用兩種情況肮帐,而遠(yuǎn)程調(diào)用有個(gè)限制,傳遞的參數(shù)類型有限制漱逸,只能傳能被字符串化的數(shù)據(jù)泪姨,或者說(shuō)只能傳能被轉(zhuǎn)成 json 的數(shù)據(jù)游沿,像 UIImage 這類對(duì)象是不行的,所以如果組件接口要考慮遠(yuǎn)程調(diào)用肮砾,這里的參數(shù)就不能是這類非常規(guī)對(duì)象诀黍,接口的定義就受限了。

用理論的話來(lái)說(shuō)就是仗处,遠(yuǎn)程調(diào)用是本地調(diào)用的子集眯勾,這里混在一起導(dǎo)致組件只能提供子集功能,無(wú)法提供像方案1那樣提供全集功能婆誓。所以這個(gè)方案是天生有缺陷的吃环,對(duì)于遺漏的這部分功能,蘑菇街使用了另一種方案補(bǔ)全洋幻,請(qǐng)看方案3郁轻。

方案3

蘑菇街為了補(bǔ)全本地調(diào)用的功能,為組件多加了另一種方案文留,就是通過(guò) protocol-class 注冊(cè)表的方式好唯。首先有一個(gè)新的中間件:

//ProtocolMediator.m 新中間件

@implementation ProtocolMediator

@property (nonatomic, storng) NSMutableDictionary *protocolCache

- (void)registerProtocol:(Protocol *)proto forClass:(Class)cls {

NSMutableDictionary *protocolCache;

[protocolCache setObject:cls forKey:NSStringFromProtocol(proto)];

}

- (Class)classForProtocol:(Protocol *)proto {

return protocolCache[NSStringFromProtocol(proto)];

}

@end

然后有一個(gè)公共Protocol文件,定義了每一個(gè)組件對(duì)外提供的接口:

//ComponentProtocol.h

@protocol BookDetailComponentProtocol

- (UIViewController *)bookDetailController:(NSString *)bookId;

- (UIImage *)coverImageWithBookId:(NSString *)bookId;

@end

@protocol ReviewComponentProtocol

- (UIViewController *)ReviewController:(NSString *)bookId;

@end

再在模塊里實(shí)現(xiàn)這些接口燥翅,并在初始化時(shí)調(diào)用 registerProtocol 注冊(cè)骑篙。

//BookDetailComponent 組件

#import "ProtocolMediator.h"

#import "ComponentProtocol.h"

#import "WRBookDetailViewController.h"

+ (void)initComponent

{

[[ProtocolMediator sharedInstance] registerProtocol:@protocol(BookDetailComponentProtocol) forClass:[self class];

}

- (UIViewController *)bookDetailController:(NSString *)bookId {

WRBookDetailViewController *detailVC = [[WRBookDetailViewController alloc] initWithBookId:param[@"bookId"]];

return detailVC;

}

- (UIImage *)coverImageWithBookId:(NSString *)bookId {

….

}

最后調(diào)用者通過(guò) protocol 從 ProtocolMediator 拿到提供這些方法的 Class,再進(jìn)行調(diào)用:

//WRReadingViewController.m 調(diào)用者

//ReadingViewController.m

#import "ProtocolMediator.h"

#import "ComponentProtocol.h"

+ (void)gotoDetail:(NSString *)bookId {

Class cls = [[ProtocolMediator sharedInstance] classForProtocol:BookDetailComponentProtocol];

id bookDetailComponent = [[cls alloc] init];

UIViewController *vc = [bookDetailComponent bookDetailController:bookId];

[[UIApplication sharedApplication].keyWindow.rootViewController.navigationController pushViewController:vc animated:YES];

}

這種思路有點(diǎn)繞森书,這個(gè)方案跟剛才兩個(gè)最大的不同就是靶端,它不是直接通過(guò) Mediator 調(diào)用組件方法,而是通過(guò) Mediator 拿到組件對(duì)象凛膏,再自行去調(diào)用組件方法杨名。

結(jié)果就是組件方法的調(diào)用是分散在各地的,沒(méi)有統(tǒng)一的入口译柏,也就沒(méi)法做組件不存在時(shí)的統(tǒng)一處理镣煮。組件1調(diào)用了組件2的方法,如果用前面兩種方式鄙麦,組件間是沒(méi)有依賴的典唇,組件1+Mediator可以單獨(dú)抽離出來(lái),只需要在Mediator里做好調(diào)用組件2方法時(shí)的異常處理就行胯府。而這種方法組件1對(duì)組件2的調(diào)用分散在各個(gè)地方介衔,沒(méi)法做這些處理,在不修改組件1代碼的情況下骂因,組件1和組件2是分不開(kāi)的炎咖。

當(dāng)然你也可以在這上面跟方案1一樣在 Mediator 對(duì)每一個(gè)組件接口 wrapper 一層,那這樣這種方案跟方案1比除了更復(fù)雜點(diǎn),其他沒(méi)什么區(qū)別乘盼。

在 protocol-class 這個(gè)方案上升熊,主要存在的問(wèn)題就是分散調(diào)用導(dǎo)致耦合,另外實(shí)現(xiàn)上會(huì)有一些繞绸栅,其他就沒(méi)什么了级野。casa 說(shuō)的 “protocol對(duì)業(yè)務(wù)產(chǎn)生了侵入,且不符合黑盒模型粹胯”腿幔” 其實(shí)并沒(méi)有這么夸張,實(shí)際上 protocol 對(duì)外提供組件方法风纠,跟方案1在 Mediator wrapper 對(duì)外提供組件方法是差不多的况鸣。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市竹观,隨后出現(xiàn)的幾起案子镐捧,更是在濱河造成了極大的恐慌,老刑警劉巖栈幸,帶你破解...
    沈念sama閱讀 211,423評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件愤估,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡速址,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,147評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門由驹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)芍锚,“玉大人,你說(shuō)我怎么就攤上這事蔓榄〔⑴冢” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 157,019評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵甥郑,是天一觀的道長(zhǎng)逃魄。 經(jīng)常有香客問(wèn)我,道長(zhǎng)澜搅,這世上最難降的妖魔是什么伍俘? 我笑而不...
    開(kāi)封第一講書人閱讀 56,443評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮勉躺,結(jié)果婚禮上癌瘾,老公的妹妹穿的比我還像新娘。我一直安慰自己饵溅,他們只是感情好妨退,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,535評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般咬荷。 火紅的嫁衣襯著肌膚如雪冠句。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,798評(píng)論 1 290
  • 那天幸乒,我揣著相機(jī)與錄音轩端,去河邊找鬼。 笑死逝变,一個(gè)胖子當(dāng)著我的面吹牛基茵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播壳影,決...
    沈念sama閱讀 38,941評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼拱层,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了宴咧?” 一聲冷哼從身側(cè)響起根灯,我...
    開(kāi)封第一講書人閱讀 37,704評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎掺栅,沒(méi)想到半個(gè)月后烙肺,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,152評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡氧卧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,494評(píng)論 2 327
  • 正文 我和宋清朗相戀三年桃笙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沙绝。...
    茶點(diǎn)故事閱讀 38,629評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡搏明,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出闪檬,到底是詐尸還是另有隱情星著,我是刑警寧澤,帶...
    沈念sama閱讀 34,295評(píng)論 4 329
  • 正文 年R本政府宣布粗悯,位于F島的核電站虚循,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏样傍。R本人自食惡果不足惜横缔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,901評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望铭乾。 院中可真熱鬧剪廉,春花似錦、人聲如沸炕檩。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至泉沾,卻和暖如春捞蚂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背跷究。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,978評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工姓迅, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人俊马。 一個(gè)月前我還...
    沈念sama閱讀 46,333評(píng)論 2 360
  • 正文 我出身青樓丁存,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親柴我。 傳聞我的和親對(duì)象是個(gè)殘疾皇子解寝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,499評(píng)論 2 348

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