合理的架構(gòu)設(shè)計(jì)

前言:當(dāng)業(yè)務(wù)需求量和團(tuán)隊(duì)規(guī)模達(dá)到一定程度后,任何一款A(yù)pp都需要考慮架構(gòu)設(shè)計(jì)的合理性。

簡單架構(gòu)向大型項(xiàng)目架構(gòu)演進(jìn)中,需要解決三個(gè)問題:
1、模塊粒度應(yīng)該如何劃分?
2、如何分層?
3、多團(tuán)隊(duì)如何寫作?
而這三條之中,第一條模塊粒度的劃分是非常關(guān)鍵的一補(bǔ),也是一個(gè)細(xì)活,最好可以在不同階段采用不同的粒度劃分模塊。

大項(xiàng)目、多人、多團(tuán)隊(duì)架構(gòu)思考

接下來,先要了解模塊粒度應(yīng)該怎么劃分的問題。

首先,項(xiàng)目規(guī)模變大后,模塊劃分必須遵循一定的原則。如果模塊劃分規(guī)則不規(guī)范不清晰,就會(huì)導(dǎo)致代碼耦合嚴(yán)重的問題,并加大架構(gòu)重構(gòu)的難度。這些問題主要表現(xiàn)在:

  • 業(yè)務(wù)需求不斷,業(yè)務(wù)開發(fā)不能停。重新劃分模塊的工作量越大,成本越高,重構(gòu)拍上日程的難度也就越大。
  • 老業(yè)務(wù)代碼年久失修,沒有注釋,修改起來需要重新梳理邏輯和關(guān)系,耗時(shí)長。

其次,搞清楚模塊粒度采用什么標(biāo)準(zhǔn)劃分,就是要遵循的原則是什么。對于iOS這種面相對象編程的開發(fā)模式來說,應(yīng)該遵循以下五個(gè)原則,即SOLID原則。

  • 單一功能原則: 對象功能要單一,不要在一個(gè)對象里添加很多功能。
  • 開閉原則: 擴(kuò)展是開放的,修改是封閉的。
  • 里氏替換原則: 子類對象是可以代替基類對象的。
  • 接口隔離原則: 接口的用途要單一,不要在一個(gè)接口上根據(jù)不同入?yún)?shí)現(xiàn)多個(gè)功能。
  • 依賴反轉(zhuǎn)原則: 方法應(yīng)該依賴抽象,不要依賴實(shí)例。iOS開發(fā)就是高層業(yè)務(wù)方法依賴于協(xié)議。

最后,需要選擇合適的粒度,切記,大型項(xiàng)目的模塊粒度過大或者過小都不合適。

其中,組件可以認(rèn)為是可組裝的、獨(dú)立的業(yè)務(wù)單元,具有高內(nèi)聚,低耦合的特性,是一種比較適中的粒度。就像用樂高拼房子一樣,每個(gè)對象就是一塊小積木。一個(gè)組件就是由一塊一塊的小積木組成的有單一功能的組合,比如門、柱子、煙囪。

在我看來,iOS 開發(fā)中的組件,不是 UI 的控件,也不是 ViewController 這種大 UI 和功能的集合。因?yàn)椋琔I 控件的粒度太小,而頁面的粒度又太大。iOS 組件,應(yīng)該是包含 UI 控件、相關(guān)多個(gè)小功能的合集,是一種粒度適中的模塊。

并且,采用組件的話,對于代碼邏輯和模塊間的通信方式的改動(dòng)都不大,完成老代碼切換也就相對容易些。我們可以先按照物理劃分,也就是將多個(gè)相同功能的類移動(dòng)到同一個(gè)文件夾下,然后做成 CocoaPods 的包進(jìn)行管理。

但是,僅做到這一步還不夠,因?yàn)楣δ苣K之間的耦合還是沒有被解除。如果沒有解除耦合關(guān)系的話,不同功能的開發(fā)還是沒法獨(dú)立開來,勉強(qiáng)開發(fā)完成后的影響范圍評估也難以確定。

所以接下來,就需要重新梳理組件之間的邏輯關(guān)系,進(jìn)行改造。

但是,組件解耦并不是說要求每個(gè)組件間都沒有耦合,組件間也需要有上下層依賴的關(guān)系。組件間的上下層關(guān)系劃分清楚了,就會(huì)容易維護(hù)和管理。而對于組件間如何分層這個(gè)問題,感覺層級最多不要超過三個(gè),可以這么設(shè)置:

  • 底層可以是與業(yè)務(wù)無關(guān)的基礎(chǔ)組件,比如網(wǎng)絡(luò)和存儲(chǔ)等;
  • 中間層一般是通用的業(yè)務(wù)組件,比如賬號、埋點(diǎn)、支付、購物車等;
  • 最上層是迭代業(yè)務(wù)組件,更新頻率最高。

這樣的三層結(jié)構(gòu),尤其有利于多個(gè)團(tuán)隊(duì)分別開發(fā)維護(hù)。比如,一開始有兩個(gè)業(yè)務(wù)團(tuán)隊(duì) A 和 B,他們在開發(fā)時(shí)既有通用的功能、賬號、埋點(diǎn)、個(gè)人頁等,也有專有的業(yè)務(wù)功能模塊,每個(gè)功能都是一個(gè)組件。

這樣,新創(chuàng)建的業(yè)務(wù)團(tuán)隊(duì) C,就能非常輕松地使用團(tuán)隊(duì) A 和 B 開發(fā)出的通用組件。而且,如果兩個(gè)業(yè)務(wù)團(tuán)隊(duì)有相同功能時(shí),對相應(yīng)的功能組件進(jìn)行簡單改造后,也能同時(shí)適用于兩個(gè)業(yè)務(wù)團(tuán)隊(duì)。

但是,不用把所有的功能都做成組件,只有那些會(huì)被多個(gè)業(yè)務(wù)或者團(tuán)隊(duì)使用的功能模塊才需要做成組件。因?yàn)椋脑斐山M件也是需要時(shí)間成本的,很少有公司愿意完全停下業(yè)務(wù)去進(jìn)行重構(gòu),而一旦決定某業(yè)務(wù)功能模塊要改成組件,就要抓住機(jī)會(huì),嚴(yán)格按照 SOLID 原則去改造組件,因?yàn)榉倒ず驮賰?yōu)化的機(jī)會(huì)可能不會(huì)再有。

多團(tuán)隊(duì)之間如何分工?

在代碼層面,我們通過組件化解決了大項(xiàng)目、多人、多團(tuán)隊(duì)架構(gòu)的問題,但是架構(gòu)問題還涉及到團(tuán)隊(duì)人員結(jié)構(gòu)上的架構(gòu)。當(dāng)公司或者集團(tuán)的 App 多了后,相應(yīng)的團(tuán)隊(duì)也就多了,為了能夠讓產(chǎn)品快速迭代和穩(wěn)定發(fā)展,也需要一個(gè)合理的團(tuán)隊(duì)結(jié)構(gòu)。在我看來,這個(gè)合理的團(tuán)隊(duì)結(jié)構(gòu)應(yīng)該是這樣的:

  • 首先,需要一個(gè)專門的基建團(tuán)隊(duì),負(fù)責(zé)業(yè)務(wù)無關(guān)的基礎(chǔ)功能組件和業(yè)務(wù)相關(guān)通用業(yè)務(wù)組件的開發(fā)。
  • 然后,每個(gè)業(yè)務(wù)都由一個(gè)專門的團(tuán)隊(duì)來負(fù)責(zé)開發(fā)。業(yè)務(wù)可以按照功能耦合度來劃分,耦合度高的業(yè)務(wù)可以劃分成單獨(dú)的業(yè)務(wù)團(tuán)隊(duì)。
  • 其次,基建團(tuán)隊(duì)人員應(yīng)該是流動(dòng)的,從業(yè)務(wù)團(tuán)隊(duì)里來,再回到業(yè)務(wù)團(tuán)隊(duì)中去。這么設(shè)計(jì)是因?yàn)闃I(yè)務(wù)團(tuán)隊(duì)和基建團(tuán)隊(duì)的邊界不應(yīng)該非常明顯,否則就會(huì)出現(xiàn)基建團(tuán)隊(duì)埋頭苦干,結(jié)果可能是做得過多、做得不夠,或著功能不好用的問題,造成嚴(yán)重的資源浪費(fèi)。

總結(jié)來講,團(tuán)隊(duì)分工要靈活,不要把人員隔離固化了,否則各干各的,做的東西相互都不用。核心上,團(tuán)隊(duì)分工還是要圍繞著具體業(yè)務(wù)進(jìn)行功能模塊提煉,去解決重復(fù)建設(shè)的問題,在這個(gè)基礎(chǔ)上把提煉出的模塊做精做扎實(shí)。

我心目中好的架構(gòu)是什么樣的?

現(xiàn)在,我們已經(jīng)可以從代碼內(nèi)外來分析 App 開發(fā)的架構(gòu)設(shè)計(jì)了,但也只是會(huì)分析了而已,腦海中并沒有明確好的架構(gòu)是什么樣的,也不知道具體應(yīng)該怎么設(shè)計(jì)。接下來,我們就帶著這兩個(gè)問題繼續(xù)看下面的內(nèi)容。

組件化是解決項(xiàng)目大、人員多的一種很好的手段,這在任何公司或團(tuán)隊(duì)都是沒有歧義的。組件間關(guān)系協(xié)調(diào)卻沒有固定的標(biāo)準(zhǔn),協(xié)調(diào)的優(yōu)劣,成為了衡量架構(gòu)優(yōu)劣的一個(gè)基本標(biāo)準(zhǔn)。所以在實(shí)踐中,一般分為了協(xié)議式和中間者兩種架構(gòu)設(shè)計(jì)方案。

協(xié)議式架構(gòu)設(shè)計(jì)主要采用的是協(xié)議式編程的思路:在編譯層面使用協(xié)議定義規(guī)范,實(shí)現(xiàn)可在不同地方,從而達(dá)到分布管理和維護(hù)組件的目的。這種方式也遵循了依賴反轉(zhuǎn)原則,是一種很好的面向?qū)ο缶幊痰膶?shí)踐。但是,這個(gè)方案的缺點(diǎn)也很明顯,主要體現(xiàn)在以下兩個(gè)方面:

1、由于協(xié)議式編程缺少統(tǒng)一調(diào)度層,導(dǎo)致難于集中管理,特別是項(xiàng)目規(guī)模變大、團(tuán)隊(duì)變多的情況下,架構(gòu)管控就會(huì)顯得越來越重要。
2、協(xié)議式編程接口定義模式過于規(guī)范,從而使得架構(gòu)的靈活性不夠高。當(dāng)需要引入一個(gè)新的設(shè)計(jì)模式來開發(fā)時(shí),我們就會(huì)發(fā)現(xiàn)很難融入到當(dāng)前架構(gòu)中,缺乏架構(gòu)的統(tǒng)一性。

雖然協(xié)議式架構(gòu)有這兩方面的局限性,但其簡單易用的特點(diǎn)依然被很多人推崇。

另一種常用的架構(gòu)形式是中間者架構(gòu)。它采用中間者統(tǒng)一管理的方式,來控制 App 的整個(gè)生命周期中組件間的調(diào)用關(guān)系。同時(shí),iOS 對于組件接口的設(shè)計(jì)也需要保持一致性,方便中間者統(tǒng)一調(diào)用。

拆分的組件都依賴于中間者,組間之間不存在相互依賴的關(guān)系。由于其他組件都會(huì)依賴于這個(gè)中間者,相互間的通信都會(huì)通過中間者統(tǒng)一調(diào)度,所以組件間的通信也就更容易管理了。在中間者上也能夠輕松添加新的設(shè)計(jì)模式,從而使得架構(gòu)更容易擴(kuò)展。

好的架構(gòu)一定是健壯的、靈活的。中間者架構(gòu)的易管控帶來的架構(gòu)更穩(wěn)固,易擴(kuò)展帶來的靈活性,所以中間者這種架構(gòu)設(shè)計(jì)模式是非常值得推薦的。casatwy 以前設(shè)計(jì)了一個(gè) CTMediator就是按照中間者架構(gòu)思路設(shè)計(jì)的。

CTMediator 使用的是運(yùn)行時(shí)解耦,可以通過開源的 CTMediator 代碼,一起查看下如何使用運(yùn)行時(shí)技術(shù)來解耦。解耦核心方法如下所示:


- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
    NSString *swiftModuleName = params[kCTMediatorParamsKeySwiftTargetModuleName];
    
    // generate target
    NSString *targetClassString = nil;
    if (swiftModuleName.length > 0) {
        targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
    } else {
        targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
    }
    NSObject *target = self.cachedTarget[targetClassString];
    if (target == nil) {
        Class targetClass = NSClassFromString(targetClassString);
        target = [[targetClass alloc] init];
    }


    // generate action
    NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
    SEL action = NSSelectorFromString(actionString);
    
    if (target == nil) {
        // 這里是處理無響應(yīng)請求的地方之一,這個(gè)demo做得比較簡單,如果沒有可以響應(yīng)的target,就直接return了。實(shí)際開發(fā)過程中是可以事先給一個(gè)固定的target專門用于在這個(gè)時(shí)候頂上,然后處理這種請求的
        [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
        return nil;
    }
    
    if (shouldCacheTarget) {
        self.cachedTarget[targetClassString] = target;
    }


    if ([target respondsToSelector:action]) {
        return [self safePerformAction:action target:target params:params];
    } else {
        // 這里是處理無響應(yīng)請求的地方,如果無響應(yīng),則嘗試調(diào)用對應(yīng)target的notFound方法統(tǒng)一處理
        SEL action = NSSelectorFromString(@"notFound:");
        if ([target respondsToSelector:action]) {
            return [self safePerformAction:action target:target params:params];
        } else {
            // 這里也是處理無響應(yīng)請求的地方,在notFound都沒有的時(shí)候,這個(gè)demo是直接return了。實(shí)際開發(fā)過程中,可以用前面提到的固定的target頂上的。
            [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
            [self.cachedTarget removeObjectForKey:targetClassString];
            return nil;
        }
    }

performTarget:action:params:shouldCacheTarget: 方法主要是對 targetName 和 actionName 進(jìn)行容錯(cuò)處理,也就是對調(diào)用方法無響應(yīng)的處理。這個(gè)方法封裝了 safePerformAction:target:params 方法,入?yún)?targetName 就是調(diào)用接口的對象,actionName 是調(diào)用的方法名,params 是參數(shù)。

從代碼中同時(shí)還能看出只有滿足 Target_ 前綴的對象和 Action 的方法才能被 CTMediator 使用。這時(shí),我們可以看出中間者架構(gòu)的優(yōu)勢,也就是利于統(tǒng)一管理,可以輕松管控制定的規(guī)則。

下面這段代碼,是使用 CTMediator 如何調(diào)用一個(gè)彈窗顯示方法的代碼示范:


[self performTarget:kCTMediatorTargetA
                 action:kCTMediatorActionShowAlert
                 params:paramsToSend
      shouldCacheTarget:NO];

可以看出,指定了對象名和調(diào)用方法名,把參數(shù)封裝成字典傳進(jìn)去就能夠直接調(diào)用該方法了。

但是,這種運(yùn)行時(shí)直接硬編碼的調(diào)用方式也有些缺點(diǎn),主要表現(xiàn)在兩個(gè)方面:

1、直接硬編碼的調(diào)用方式,參數(shù)是以 string 的方法保存在內(nèi)存里,雖然和將參數(shù)保存在 Text 字段里占用的內(nèi)存差不多,同時(shí)還可以避免.h 文件的耦合,但是其對代碼編寫效率的降低也比較明顯。

2、由于是在運(yùn)行時(shí)才確定的調(diào)用方法,調(diào)用方式由 [obj method] 變成 [obj performSelector:@""]。這樣的話,在調(diào)用時(shí)就缺少類型檢查,是個(gè)很大的缺憾。因?yàn)椋绻椒ê蛥?shù)比較多的時(shí)候,代碼編寫效率就會(huì)比較低。

但是我們可以通過casatwy給的建議完美解決這兩個(gè)問題。下面是 casatwy 的原話。

CTMediator 本質(zhì)就是一個(gè)方法,用來接收 target、action、params。由于 target、action 都是字符串,params 是字典,對于調(diào)用者來說十分不友好,因?yàn)檎{(diào)用者要寫字符串,而且調(diào)用的時(shí)候若是不看文檔,他也不知道這個(gè)字典里該塞什么東西。

所以實(shí)際情況中,調(diào)用者是不會(huì)直接調(diào)用 CTMediator 的方法的。那調(diào)用者怎么發(fā)起調(diào)用呢?通過響應(yīng)者給 CTMediator 做的 category 或者 extension 發(fā)起調(diào)用。

category 或 extension 以函數(shù)聲明的方式,解決了參數(shù)的問題。調(diào)用者看這個(gè)函數(shù)長什么樣子,就知道給哪些參數(shù)。在 category 或 extension 的方法實(shí)現(xiàn)中,把參數(shù)字典化,順便把 target、action 這倆字符串寫死在調(diào)用里。

于是,對于調(diào)用者來說,他就不必查文檔去看參數(shù)怎么給,也不必?fù)?dān)心 target、action 字符串是什么了。這個(gè) category 是一個(gè)獨(dú)立的 Pod,由響應(yīng)者業(yè)務(wù)的開發(fā)給到。

所以,當(dāng)一個(gè)工程師開發(fā)一個(gè)業(yè)務(wù)的時(shí)候,他會(huì)開發(fā)兩個(gè) Pod,一個(gè)是 category Pod,一個(gè)是自己本身的業(yè)務(wù) Pod。這樣就完美解決了 CTMediator 它自身的缺點(diǎn)。

對于調(diào)用者來說,他不會(huì)直接依賴 CTMediator 去發(fā)起調(diào)用,而是直接依賴 category Pod 去發(fā)起調(diào)用的。這么一來,CTMediator 方案就完美了。

然后還有一點(diǎn)可能需要強(qiáng)調(diào):基于 CTMediator 方案的工程,每一個(gè)組件無所謂是 OC 還是 Swift,Pod 也無所謂是 category 還是 extension。也就是說,假設(shè)一個(gè)工程由 100 個(gè)組件組成,那可以是 50 個(gè) OC、50 個(gè) Swift。因?yàn)?CTMediator 抹去了不同語言的組件之間的隔閡,所以大家老的 OC 工程可以先應(yīng)用 CTMediator,把組件拆出來。然后新的業(yè)務(wù)來了,用 Swift 寫,等有空的時(shí)候再把老的 OC 改成 Swift,或者不改,都是沒問題的。

不過,解耦的精髓在于業(yè)務(wù)邏輯能夠獨(dú)立出來,并不是形式上的解除編譯上的耦合(編譯上解除耦合只能算是解耦的一種手段而已)。所以,在考慮架構(gòu)設(shè)計(jì)時(shí),我們更多的還是需要在功能邏輯和組件劃分上做到同層級解耦,上下層依賴清晰,這樣的結(jié)構(gòu)才能夠使得上層組件易插拔,下層組件更穩(wěn)固。而中間者架構(gòu)模式更容易維護(hù)這種結(jié)構(gòu),中間者的易管控和易擴(kuò)展性,也使得整體架構(gòu)能夠長期保持穩(wěn)健與活力。所以,中間者架構(gòu)就是我心目中好的架構(gòu)。

小結(jié)

架構(gòu)的設(shè)計(jì)絕對不是要等到工程到了燃眉之急之時(shí),再去環(huán)顧其他公司或團(tuán)隊(duì)在用什么架構(gòu),然后拍腦袋拿一個(gè)過來,來次大重構(gòu)。好的架構(gòu),需要在業(yè)務(wù)開發(fā)過程中及早發(fā)現(xiàn)開發(fā)的痛點(diǎn),進(jìn)行有針對性的改良,不然就會(huì)和實(shí)際開發(fā)越走越遠(yuǎn)。

比如,某個(gè)業(yè)務(wù)模塊的邏輯非常復(fù)雜、狀態(tài)有很多,這時(shí)我們就需要在架構(gòu)層面考慮如何處理會(huì)更方便,改動(dòng)最小的支持狀態(tài)機(jī)模式,又或者在開始架構(gòu)設(shè)計(jì)時(shí)就多考慮如何將架構(gòu)設(shè)計(jì)的具有更高的易用性和可擴(kuò)展性。

好的架構(gòu)是能夠在一定的規(guī)范內(nèi)同時(shí)支持高靈活度,這種度的把握是需要架構(gòu)師長期跟隨團(tuán)隊(duì)開發(fā),隨著實(shí)際業(yè)務(wù)需求的演進(jìn)進(jìn)行分析和把控的。

在項(xiàng)目大了,人員多了的情況下,好的架構(gòu)一定是不簡單的,不通用的,但一定是接地氣的,這樣才能更適合自己的團(tuán)隊(duì),才能夠用得上。那些大而全,炫技,脫離業(yè)務(wù)開發(fā)需求的架構(gòu)是沒法落地的。

作為一名普通的開發(fā)者,除了日常需求開發(fā)和技術(shù)方案調(diào)研、設(shè)計(jì)外,還需要了解自己所在項(xiàng)目的整體架構(gòu)是怎樣的,想想架構(gòu)上哪些地方是不夠好需要改進(jìn)的,業(yè)界有哪些好的架構(gòu)思想是可以落地到自己項(xiàng)目中的。有了從項(xiàng)目整體上去思考的意識,才能夠站在更高的視角上去思考問題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,125評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,506評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,402評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,934評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,168評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,690評論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,596評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,784評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,027評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,398評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,743評論 2 370