企業(yè)模式

PHP是一個為Web開發(fā)而設(shè)計的語言。在PHP5之后,PHP開始大力支持對象,因此你可以享受到設(shè)計模式帶來的好處,就像使用其他面向?qū)ο笳Z言(特別是Java)那樣。
本章將舉一個簡單的例子來說明設(shè)計模式的使用。請注意,選擇使用某種模式時,并不一定也要使用與該模式配合良好的其他所有模式,而且示例中介紹的部署這些模式的方法也不是唯一可行的方法。示例主要用于幫助理解模式的核心思想,你可以從中提取自己需要的內(nèi)容,并應(yīng)用于實際開發(fā)當(dāng)中。
本章介紹的內(nèi)容很多,是全書最長和最復(fù)雜的章節(jié),相信讀者很難一次性讀完。本章分為一個簡要介紹和兩個主要部分。你可以在讀完其中某部分時休息一下。
12.1節(jié)中介紹了一些獨(dú)立的模式。盡管這些內(nèi)容有時是相互關(guān)聯(lián)的,但可以直接跳到任何一個你想了解的模式進(jìn)行閱讀和學(xué)習(xí),在你有空的時候再閱讀其他相關(guān)模式的內(nèi)容。
本章包括以下內(nèi)容。

  • 架構(gòu)概述:企業(yè)應(yīng)用程序分層。
  • 注冊(Registry)模式:管理應(yīng)用程序數(shù)據(jù)。
  • 表現(xiàn)層:管理和響應(yīng)用戶請求,并把數(shù)據(jù)呈現(xiàn)給用戶。
  • 業(yè)務(wù)邏輯層:處理系統(tǒng)的真實任務(wù)------解決業(yè)務(wù)問題。

12.1 架構(gòu)概述

因為涉及的內(nèi)容比較廣泛,所以首先概述一下模式,然后介紹如何構(gòu)建分層的應(yīng)用程序。

12.1.1 模式

下面是本章將要討論的設(shè)計模式。你可以從頭看到尾,也可以根據(jù)需要和興趣選擇性地閱讀。注意命令模式?jīng)]有在此單獨(dú)介紹(在第11章介紹過),但會在前端控制器和應(yīng)用控制器模式中提及

  • 注冊表:該模式用于使數(shù)據(jù)對進(jìn)程中所有的類都有效。通過謹(jǐn)慎的序列化操作,注冊表對象可以用于存儲跨會話甚至跨應(yīng)用實例的數(shù)據(jù)。
  • 前端控制器:在規(guī)模較大的系統(tǒng)中,該模式可用于盡可能靈活地管理各種不同的命令和視圖。
  • 應(yīng)用控制器:創(chuàng)建一個類來管理視圖邏輯和命令選擇。
  • 模板視圖:創(chuàng)建模板來處理和顯示用戶界面,在顯示標(biāo)記中加入動態(tài)內(nèi)容。盡量少使用原始代碼。
  • 頁面控制器:頁面控制器滿足和前端控制器相同的需求,但較為輕量級,靈活性也小一些。如果想快速得到結(jié)果而且系統(tǒng)也不太復(fù)雜的話,可以使用這種模式管理請求和處理頁面邏輯。
  • 事務(wù)腳本:如果想要快速完成某個任務(wù),可以使用本模式。通過簡單的規(guī)劃,用“過程式”的代碼來實現(xiàn)程序邏輯。但本模式的可伸縮性不佳。
  • 領(lǐng)域模型:和事務(wù)腳本相反,使用本模式可以為業(yè)務(wù)參與者和過程構(gòu)建基于對象的模型。

12.1.2 應(yīng)用程序和層

本章大部分模式是用來使程序中不同的“層”(tier,也稱為layer)獨(dú)立工作的。就像類的使命是執(zhí)行特定的任務(wù),企業(yè)應(yīng)用系統(tǒng)中的層也是如此,不過更為粗獷。圖12-1展示了一個系統(tǒng)中分工明確的各個層。

圖片.png

圖12-1所示的結(jié)構(gòu)并不是固定不變的,其中一些層可以合并,而且層之間的交互策略可能根據(jù)系統(tǒng)的復(fù)雜度而不同。無論如何,圖12-1展示的模型強(qiáng)調(diào)靈活性和重用,而許多企業(yè)應(yīng)用就是根據(jù)“靈活”和“重用”的原則進(jìn)行擴(kuò)展的。

  • 視圖層包括系統(tǒng)用戶實際看到和交互的界面。它負(fù)責(zé)顯示用戶請求的結(jié)果及傳遞新的請求給系統(tǒng)。
  • 命令和控制層處理用戶的請求。它委托業(yè)務(wù)邏輯層處理和滿足請求,然后選擇最合適的視圖,將結(jié)果顯示給用戶。實際上,這個層和視圖層常常合并為表現(xiàn)層。即使這樣,顯示的任務(wù)應(yīng)當(dāng)嚴(yán)格地與請求處理和業(yè)務(wù)邏輯調(diào)用分離開來。
  • 業(yè)務(wù)邏輯層負(fù)責(zé)根據(jù)請求執(zhí)行業(yè)務(wù)操作。它執(zhí)行需要的計算并整理結(jié)果數(shù)據(jù)。
  • 數(shù)據(jù)層負(fù)責(zé)保存和獲取系統(tǒng)中的持久信息。在某些系統(tǒng)中,命令和控制層使用數(shù)據(jù)層來獲取他所需要的業(yè)務(wù)對象。但在其他系統(tǒng)中,數(shù)據(jù)層通常盡可能地被隱藏起來。
    那么為什么要用這種方式劃分系統(tǒng)呢?答案是解耦(decoupling)。通過分離業(yè)務(wù)邏輯層與視圖層,當(dāng)添加新的接口到系統(tǒng)時,系統(tǒng)內(nèi)部只需要做很小的改動。
    假設(shè)有一個管理時間列表的系統(tǒng)。終端用戶需要一個漂亮的HTML接口,而維護(hù)系統(tǒng)的管理員可能需要一個命令行接口來構(gòu)建自動化系統(tǒng),同時,你可能需要開發(fā)支持手機(jī)和其他手持設(shè)備訪問的版本,甚至可能考慮使用REST式API或SOAP等協(xié)議。
    如果你以前把系統(tǒng)的底層邏輯和HTML視圖層混合在一起(盡管這種寫法備受批評,但在PHP項目中依然很普遍),上面所提的這些需求將會讓你不得不重寫代碼。另一方面,如果已經(jīng)創(chuàng)建了分層的系統(tǒng),就可以直接使用新的顯示方案而不用重新考慮業(yè)務(wù)邏輯和數(shù)據(jù)層。
    同樣,項目的持久性策略也可能改變。你應(yīng)該能夠在對系統(tǒng)的其他層影響最小的情況下更換存儲模型。
    將系統(tǒng)分層的另一個原因是測試。Web應(yīng)用程序是很難測試的。任何一種自動測試在需要在一端解析HTML接口并在另一端使用在線數(shù)據(jù)庫時都會很為難。也就是說,測試工作必須運(yùn)行在完全部署的系統(tǒng)上,并且冒著破壞本應(yīng)受保護(hù)的真實系統(tǒng)的風(fēng)險。在分層系統(tǒng)中,任何需要與其他層直接打交道的類通常都擴(kuò)展自抽象父類或者實現(xiàn)了同一個接口。這個父類型可以支持多態(tài)。在測試環(huán)境中,一個完整的層可以被一組虛擬的對象(通常稱為stub或mock對象)所代替。例如,通過這種方法,我們可以使用虛擬的數(shù)據(jù)層來測試業(yè)務(wù)邏輯層。你可以在第18章讀到更多關(guān)于測試的內(nèi)容。
    即使系統(tǒng)只有一個簡單的接口,并且你覺得測試時多余的時候,分層仍是非常有用的。通過創(chuàng)建獨(dú)立分工的層,可以構(gòu)建一個易于擴(kuò)展和調(diào)試的系統(tǒng)。將具有同樣功能的代碼放在同一個地方可以減少代碼重復(fù)(而不是將系統(tǒng)和數(shù)據(jù)庫調(diào)用或顯示方案綁定在一起),因此添加功能到系統(tǒng)中會相對簡單,因為這些改變是縱向而不是橫向的。
    在分層系統(tǒng)中,一個新功能可能需要一個新的接口組件、額外的請求處理、更多的業(yè)務(wù)邏輯和對存儲機(jī)制的修改。這些修改是縱向的。在一個沒有分層的系統(tǒng)中,如果要增加新的功能,則可能需要記住5個甚至更多和數(shù)據(jù)庫相關(guān)的頁面。新的接口可能會在數(shù)十個地方被調(diào)用,因此需要為系統(tǒng)增加這部分的代碼。這就是橫向的修改。
    當(dāng)然,實際上我們并不能完全避免這種橫向依賴,特別當(dāng)修改頁面的導(dǎo)航部分的時候。盡管如此,一個分層的系統(tǒng)有助于最小化橫向修改的需要。
    本章所有例子都圍繞一個虛擬的事件列表系統(tǒng),系統(tǒng)的名稱叫Woo,它是What's On Outside(外頭發(fā)生了什么事)的縮寫。
    系統(tǒng)由場所(venue,如劇院、俱樂部和電影院)、空間(space,如屏幕1和樓上)和事件(event,如電影The Long Good Friday、The Importance of Being Earnest)組成。
    本系統(tǒng)的操作包括創(chuàng)建場所、添加空間到場所和列出系統(tǒng)中的所有場所。
    記住,本章的目標(biāo)是闡述主要的企業(yè)設(shè)計模式而不是構(gòu)建一個實際系統(tǒng)。由于設(shè)計模式之間常常相互依賴,本章中的大部分示例也常常會互相重疊,以充分利用本章其他地方介紹的基礎(chǔ)知識。本章的代碼主要是用來解釋企業(yè)模式的概念,因此無法符合實際系統(tǒng)中的所有標(biāo)準(zhǔn),甚至為了簡潔起見,還忽略了錯誤檢查。讀者應(yīng)該把這些例子當(dāng)成學(xué)習(xí)設(shè)計模式的途徑,不要直接當(dāng)做框架或程序中的一部分。

12.2 企業(yè)架構(gòu)之外的基礎(chǔ)模式

12.2.1 注冊表

12.3 表現(xiàn)層

當(dāng)一個請求到達(dá)系統(tǒng)時,系統(tǒng)必須能夠理解請求中的需求是什么,然后調(diào)用適當(dāng)?shù)臉I(yè)務(wù)邏輯進(jìn)行處理,最后返回相應(yīng)結(jié)果。對于簡單的程序,整個過程可能完全放在視圖之中,只有重量級的邏輯和持久化操作相關(guān)的代碼才放在封裝好的類庫中。
注解:一個視圖是指視圖層中一個單獨(dú)的元素。它可能是一個PHP頁面(或視圖元素集合),負(fù)責(zé)顯示數(shù)據(jù)和讓用戶生成新請求。在像Smarty這樣的模板系統(tǒng)中,一個視圖即指一個模板。
隨著系統(tǒng)的增長,這種默認(rèn)方案不能滿足處理請求、調(diào)用業(yè)務(wù)邏輯和派發(fā)視圖的要求。
本節(jié)我們將研究表現(xiàn)層管理以上3個主要功能的策略。視圖層與命令和控制層的邊界通常很模糊,因此我們常把這兩個層統(tǒng)稱為“表現(xiàn)層”。

12.3.1 前端控制器

本模式和傳統(tǒng)PHP應(yīng)用程序的“多入口”方式相反。前端控制器模式用一個中心來處理所有到來的請求,最后調(diào)用視圖來講結(jié)果呈現(xiàn)給用戶。前端控制器模式是Java企業(yè)應(yīng)用的核心模式之一。本模式在《J2EE核心模式》中有詳細(xì)的講解,它同時也是最有影響力的企業(yè)模式之一。在PHP中,這個模式并沒有受到廣泛喜愛,部分原因是初始化前端控制器所需要的開銷會導(dǎo)致系統(tǒng)性能下降。
現(xiàn)在我寫的大部分系統(tǒng)都開始向前端控制器模式轉(zhuǎn)移。雖然我有時沒有使用完整的前端控制器模式,但是我發(fā)現(xiàn)在項目中使用前端控制器模式確實可以提供我需要的靈活性。

  1. 問題
    當(dāng)請求可以發(fā)送到系統(tǒng)中多個地方時,我們很難避免代碼重復(fù)。你可能需要驗證用戶、把術(shù)語翻譯成多種語言或者只訪問公用數(shù)據(jù)。當(dāng)多個頁面都要執(zhí)行同一個操作時,我們可以從一個頁面復(fù)制該操作相關(guān)的代碼并粘貼到另一個頁面。但是這樣的話,當(dāng)需要修改系統(tǒng)中某個部分時,其他部分也要隨著改變,給代碼維護(hù)帶來困難。因此我們要盡量避免這種情況。當(dāng)然,首先要做的是把公共操作集中到類庫代碼中。但即使這樣,對庫函數(shù)和方法的調(diào)用代碼仍然會分布到系統(tǒng)中各個部分。
    當(dāng)系統(tǒng)控制器和視圖混雜在一起時,管理視圖的切換和選擇是另一個難點(diǎn)。在一個復(fù)雜的系統(tǒng)中,隨著輸入和邏輯層中操作的成功執(zhí)行,一個視圖中的提交動作可能會產(chǎn)生任意數(shù)目的結(jié)果頁面。從一個視圖跳到另一個視圖時,可能會產(chǎn)生混亂,特別當(dāng)某個視圖被用在多個地方的時候。
  2. 實現(xiàn)
    在核心部分,前端控制器模式定義了一個中心入口,每個請求都要從這個入口進(jìn)入系統(tǒng)。前端控制器處理請求并選擇要執(zhí)行的操作。操作通常都定義在特定的Command對象中。Command對象是根據(jù)命令模式組織的。
    圖12-4展示了一個前端控制器的結(jié)構(gòu)。
圖片.png

實際開發(fā)時,你可能會部署一些助手類來協(xié)助控制器的處理過程,但現(xiàn)在我們還是先從控制器的核心部分開始研究。下面是一個簡單的Controller類:

namespace woo\controller;

//...
class Controller{
    private $applicationHelper;
    private function __construct(){}

    static function run(){
        $instance = new Controller();
        $instance->init();
        $instance->handleRequest();
    }

    function init(){
        $applicationHelper = ApplicationHelper::instance();
        $applicationHelper->init();
    }

    function handleRequest(){
        $request = new \woo\controller\Request();
        $cmd_r = new \woo\command\CommandResolver();
        $cmd = $cmd_r->getCommand($request);
        $cmd->execute($request);
    }
}

這個Controller類非常簡單,而且沒有考慮錯誤處理。系統(tǒng)中的控制器負(fù)責(zé)分配任務(wù)給其他類。其他類完成了絕大部分實際工作。
run()只是一個便捷方法,用于調(diào)用init()和handleRequest()。run()是靜態(tài)方法,而且本類的構(gòu)造方法被聲明為private,因此客戶端代碼只能通過run()方法來實例化控制器類,并執(zhí)行相關(guān)操作。我們可以使用只包含兩行代碼的index.php文件來完成這個工作:

require("woo/controller/Controller.php");
\woo\controller\Controller::run();

init()和handleRequest()方法的不同體現(xiàn)了PHP的特性。在某些編程語言中,init()只在應(yīng)用第一次啟動時運(yùn)行,而handleRequest()在用戶的每個請求到來時運(yùn)行。盡管init()在每次請求中都會被調(diào)用,但是這個類還是注意到了啟動和請求處理間的區(qū)別。
init()方法中獲得ApplicationHelper(應(yīng)用程序助手)類的一個對象實例。這個類的作用是管理應(yīng)用程序的配置信息。控制器的init()方法調(diào)用ApplicaiontHelper中同名的init()方法,用于初始化應(yīng)用程序要使用的數(shù)據(jù)。
handleRequest()方法通過CommandResolver來獲取一個Command對象,然后調(diào)用Command對象的execute()方法來執(zhí)行實際操作。

  • 應(yīng)用程序助手
    ApplicationHelper類并不是前端控制器的核心,但前端控制器通常都需要通過應(yīng)用助手類來獲取基本的配置數(shù)據(jù),因此我們我們需要討論一下獲取配置數(shù)據(jù)的策略。下面是一個簡單的ApplicationHelper:
namespace woo\controller;
// ...
class ApplicationHelper{
    private static $instance;
    private $config = "/tmp/data/woo_options.xml";

    private function __construct(){}

    static function instance(){
        if(!self::$instance){
            self::$instance = new self();
        }
        return self::$instance;
    }

    function init(){
        $dsn = \woo\base\ApplicationRegistry::getDSN();
        if(!is_null($dsn)){
            return;
        }
        $this->getOptions();
    }

    private function getOptions(){
        $this->ensure(file_exists($this->config),"Could not find options file");
        $options = SimpleXml_load_file($this->config);
        print get_class($options);
        $dsn = (string)$options->dsn;
        $this->ensure($dsn, "No DSN found");
        \woo\base\ApplicationRegistry::setDSN($dsn);
        // 設(shè)置其他值
    }

    private function ensure($expr, $message){
        if(!$expr){
            throw new \woo\base\AppException($message);
        }
    }
}

這個類的作用是讀取配置文件中的數(shù)據(jù)并使客戶端代碼可以訪問這些數(shù)據(jù)。可以看到,這個類實現(xiàn)了單例模式。使用單例模式使它能夠為系統(tǒng)中所有的類服務(wù)。另外,你也可以把這個類的代碼當(dāng)成一個標(biāo)準(zhǔn)類并確保它被傳遞給其他感興趣的對象。本書在第9章及本章的前面部分已經(jīng)討論了使用單例模式需要注意的問題。
現(xiàn)在我們已經(jīng)實現(xiàn)了ApplicationRegistry(應(yīng)用注冊表),我們還應(yīng)重構(gòu)代碼,把ApplicationHelper改寫為注冊表,而不是兩個任務(wù)重疊的單例對象。重構(gòu)代碼的建議前一節(jié)中已經(jīng)提過(將ApplicationRegistry的核心功能從領(lǐng)域?qū)ο蟮拇嫒≈蟹蛛x出來),留給讀者當(dāng)做練習(xí)。
因此init()方法只負(fù)責(zé)加載配置數(shù)據(jù)。實際上,它檢查ApplicationRegistry,看數(shù)據(jù)是否已經(jīng)被緩存。如果Registry對象中的值已經(jīng)存在,init()就什么都不做。如果系統(tǒng)初始化要做大量工作,這樣的緩存機(jī)制是很有用的。在將應(yīng)用程序初始化和獨(dú)立請求相分離的編程語言中,可以使用復(fù)雜的初始化操作。但在PHP中,你不得不盡量使用緩存來減少初始化操作。
緩存可以有效地保證復(fù)雜而且耗費(fèi)時間的初始化過程只在第一次請求時發(fā)生,而之后所有的請求都能從中受益。
如果是第一次運(yùn)行(或者緩存文件已被刪除------這是一種簡單而有效的強(qiáng)制重新讀取配置信息的方法),getOptioins()方法將被調(diào)用。
在現(xiàn)實世界中,我們需要做比示例代碼更多的工作。示例中所做的工作只是獲取一個DSN。首先,getOptions()方法檢查配置文件是否存在(路徑存放在$config屬性中),然后從配置文件中加載XML數(shù)據(jù)并設(shè)置DSN。
注解:在這些例子中,ApplicationRegistry和ApplicationHelper都使用了硬編碼的文件路徑。在實際項目中,這些文件路徑應(yīng)該是可配置的而且可以從一個注冊表對象或一個配置對象中獲取。實際的路徑可以在安裝時用構(gòu)建工具(如PEAR或Phing,參見第15章和第19章)來設(shè)置。
注意類中使用了一個技巧來拋出異常,避免了在代碼中到處使用下面這樣的條件語句和throw語句:

if(!file_exists($this->config)){
    throw new \woo\base\AppException("Could not find options file");
}

這個技巧就是ApplicationHelper類在ensure()方法中集合了檢測表達(dá)式和throw語句。只用一行代碼就能確定條件是否為真(如果不為真,則拋出異常):

$this->ensure(file_exists($this->config),"Could not find options file");

緩存對系統(tǒng)開發(fā)者和使用者都有好處。系統(tǒng)可以方便地維護(hù)一個易于使用的XML配置文件,同時使用緩存意味著系統(tǒng)能以很快的速度訪問配置文件中的數(shù)據(jù)。當(dāng)然,如果類的用戶還是程序員,或者并不經(jīng)常修改配置,你可以直接在助手類中(或者是用一個單獨(dú)的文件)包含PHP數(shù)據(jù)結(jié)構(gòu),不用把配置數(shù)據(jù)單獨(dú)放到XML文件中。雖然這種寫法有風(fēng)險,但是代碼執(zhí)行效率最高。

  • 命令解析器
    控制器需要通過某種策略來決定如何解釋一個HTTP請求,然后調(diào)用正確的代碼來滿足這個請求。你可以很容易地在Controller類中包含這個策略,但我更喜歡使用一個特定的類來完成這個任務(wù)。因為這樣的代碼易于重構(gòu)和實現(xiàn)多態(tài)。
    前端控制器通常通過運(yùn)行一個Command對象(本書在第11章中介紹過命令模式)來調(diào)用應(yīng)用程序。Command對象通常根據(jù)請求中的參數(shù)或URL的結(jié)構(gòu)(例如,可以使用Apache配置來確定URL中的哪個字段用于選擇命令)來決定選擇哪個命令。在下面的例子中,我們將使用一個簡單的參數(shù)cmd。
    有多種方法可以用來根據(jù)給定的參數(shù)選擇命令。你可以在一個配置文件或一個數(shù)據(jù)結(jié)構(gòu)(邏輯方案)中測試該參數(shù),或者直接查找文件系統(tǒng)(物理方案)中是否存在與參數(shù)相對應(yīng)的類文件。
    邏輯方案更靈活一些,但是工作量也更大些(包括設(shè)置和維護(hù))。你可以在12.3.2節(jié)找到使用該方案的例子。
    上一章介紹過一個使用物理方案的命令工廠的例子。下面對該例子做微小改動,使用反射(reflection)來增強(qiáng)安全性:
namespace woo\command;
//...
class CommandResolver{
    private static $base_cmd;
    private static $default_cmd;

    function __construct(){
        if(!self::$base_cmd){
            self::$base_cmd = new \ReflectionClass("\woo\command\Command");
            self::$default_cmd = new DefaultCommand();
        }
    }

    function getCommand( \woo\controller\Request $request){
        $cmd = $request->getProperty('cmd');
        $sep = DIRECTORY_SEPARATOR;
        if(!$cmd){
            return self::$default_cmd;
        }
        $cmd = str_replace(array('.', $sep), "", $cmd);
        $filepath = "woo{$sep}command{$sep}{$cmd}.php";
        $classname = "woo\\command\\{$cmd}";
        if(file_exists($filepath)){
            @require_once("$filepath");
            if(class_exists($classname)){
                $cmd_class = new ReflectionClass($classname);
                if($cmd_class->isSubClassOf(self::$base_cmd)){
                    return $cmd_class->newInstance();
                }else{
                    $request->addFeedback("command '$cmd' is not a Command");
                }
            }
        }
        $request->addFeedback("command '$cmd' not found");
        return clone self::$default_cmd;
    }
}

這個簡單的類用于查找請求中包含的cmd參數(shù)。假設(shè)參數(shù)被找到,并與命令目錄中的類文件相匹配,而該文件也正好包含了cmd類,則該方法創(chuàng)建并返回相應(yīng)類的實例。
如果其中任意條件未滿足,getCommand()方法使用默認(rèn)的Command對象。
你或許想知道,為什么實例化Command類時不需要提供參數(shù):

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

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