MVC、MVP翠勉、MVVM妖啥,我到底該怎么選?

本文由玉剛說寫作平臺提供寫作贊助对碌,版權(quán)歸玉剛說微信公眾號所有
原作者:AndroFarmer
版權(quán)聲明:未經(jīng)玉剛說許可荆虱,不得以任何形式轉(zhuǎn)載

前言

MVC、MVP朽们、MVVM是我們工作和面試中都比較重要的一塊怀读,但很多時候我們卻有點迷惑。比如看了好多篇文章都搞不懂MVC到底是個啥本來想寫個MVP寫著寫著就變成MVC了骑脱,到底Databing和MVVM之間有啥見不得人的關(guān)系菜枷。本篇文章主要從發(fā)展的角度來介紹,如mvp叁丧,mvvm的出現(xiàn)都是為了解決前者的哪些問題啤誊。如果你有同樣的疑問,本篇文章可能會給你帶來一點收獲拥娄。但是架構(gòu)和設(shè)計模式相對來說不是那么容易捉摸透的東西蚊锹,很多需要經(jīng)過實踐才能體會,另外由于本人水平有限稚瘾,如果寫的不對或者不嚴(yán)謹(jǐn)?shù)牡胤侥道ィ埐灰蛭摇?/p>

MVC

可能由于MVP、MVVM的興起摊欠,MVC在android中的應(yīng)用變得越來越少了丢烘,但MVC是基礎(chǔ),理解好MVC才能更好的理解MVP,MVVM些椒。因為后兩種都是基于MVC發(fā)展而來的铅协。

1、MVC眼花繚亂設(shè)計圖

我們從網(wǎng)上搜索mvc相關(guān)資料時摊沉,如果你多看幾篇文章的話可能會發(fā)現(xiàn)狐史,好像他們介紹的設(shè)計圖都不太一樣,這里羅列了大部分的設(shè)計圖


mvc1.png

mvc2.png

mvc3.png

mvc4.png
2、MVC設(shè)計圖解釋

到底上面列出的設(shè)計圖哪個才是對的骏全。其實都是對的苍柏。為什么這么說呢,這得從mvc的發(fā)展說起姜贡。
MVC框架模式最早由Trygve Reenskaug 于1978年在Smalltalk-80系統(tǒng)上首次提出试吁。經(jīng)過了這么多年的發(fā)展,當(dāng)然會演變出不同的版本楼咳,但核心沒變依舊還是三層模型Model-View-Control熄捍。

3、MVC三層之間的關(guān)系

箭頭→代表的是一種事件流向母怜,并不一定要持有對方余耽,比如上圖中model→view的事件流向,view可以通過注冊監(jiān)聽器的形式得到model發(fā)來的事件苹熏。在設(shè)計中model view controller之間如果要通訊碟贾,盡量設(shè)計成不直接持有,這樣方便復(fù)用轨域。也符合mvc的設(shè)計初衷
在android中三者對應(yīng)的關(guān)系如下:


視圖層(View)
對應(yīng)于xml布局文件和java代碼動態(tài)view部分

控制層(Controller)
MVC中Android的控制層是由Activity來承擔(dān)的袱耽,Activity本來主要是作為初始化頁面,展示數(shù)據(jù)的操作干发,但是因為XML視圖功能太弱朱巨,所以Activity既要負責(zé)視圖的顯示又要加入控制邏輯,承擔(dān)的功能過多枉长。

模型層(Model)
針對業(yè)務(wù)模型蔬崩,建立的數(shù)據(jù)結(jié)構(gòu)和相關(guān)的類,它主要負責(zé)網(wǎng)絡(luò)請求搀暑,數(shù)據(jù)庫處理,I/O的操作跨琳。

由于android中有個god object的存在activity自点,再加上android中xml布局的功能性太弱,所以activity承擔(dān)了絕大部分的工作脉让。所以在android中mvc更像是這種形式:



因為activity扮演了controller和view的工作桂敛,所以controller和view不太好徹底解耦,但是在一定程度上我們還是可以解耦的溅潜。
Talk is cheap. Show me the code. 扯了這么多术唬,我們來看點代碼。

4滚澜、MVC sample

通過代碼來看下粗仓,mvc在android中的實現(xiàn)


結(jié)構(gòu)很簡單,這里介紹下其中的關(guān)鍵代碼

public interface BaseModel {
    void onDestroy();
}

BaseModel顧名思義就是所有業(yè)務(wù)邏輯model的父類,這里的onDestroy()方法用于跟activity或者fragment生命周期同步借浊,在destroy做一些銷毀操作

public interface Callback1<T> {
    void onCallBack(T t);
}
public interface Callback2<T,P> {
    void onCallBack(T t,P p);
}

Callback是根據(jù)View或者Controller調(diào)用Model時回調(diào)的參數(shù)個數(shù)選擇使用

public class SampleModel  implements BaseModel{

    public void  getUserInfo(String uid,Callback1<UserInfo> callback)
    {

        UserInfo userInfo= new HttpUtil<UserInfo>().get(uid);
        callback.onCallBack(userInfo);

    }

    @Override
    public void onDestroy() {

    }

    public class UserInfo
    {
        private int age;
        private String name;

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

SampleModel是我們業(yè)務(wù)邏輯的具體實現(xiàn)

public class SampleActivity extends AppCompatActivity {
    private SampleModel sampleModel;
    Button button;
    EditText textView;
    TextView tvAge,tvName;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sample);
        sampleModel=new SampleModel();
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                getUserInfo(textView.getText().toString());
            }
        });

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        sampleModel.onDestroy();
    }

    /**
     * 獲取用戶信息
     * @param uid
     */
    private void getUserInfo(String uid)
    {
        sampleModel.getUserInfo(uid, new Callback1<SampleModel.UserInfo>() {
            @Override
            public void onCallBack(SampleModel.UserInfo userInfo) {
                setDataToView(userInfo);
            }
        });
    }

    /**
     * 設(shè)置用戶信息到view
     */
    private void setDataToView(SampleModel.UserInfo userInfo)
    {
        tvAge.setText(userInfo.getAge());
        tvName.setText(userInfo.getName());
    }

}


前面說了Activity充當(dāng)View和Controller塘淑,但是我們依然要區(qū)分到底哪一部分是View的操作,哪一部分是Controller的操作蚂斤。
我們分析下事件的流向
button點擊事件的觸發(fā):View→Controller
獲取用戶信息事件的觸發(fā):Controller→Model
綁定用戶信息到View:Controller→View
至此MVC就講完了

5存捺、MVC總結(jié)

我們這里根據(jù)sample來總結(jié)下:

  • 具有一定的分層,model徹底解耦曙蒸,controller和view并沒有解耦
  • 層與層之間的交互盡量使用回調(diào)或者去使用消息機制去完成捌治,盡量避免直接持有
  • controller和view在android中無法做到徹底分離,但在代碼邏輯層面一定要分清
  • 業(yè)務(wù)邏輯被放置在model層纽窟,能夠更好的復(fù)用和修改增加業(yè)務(wù)

MVP

1肖油、MVP說明

MVP跟MVC很相像,文章開頭列出了很多種MVC的設(shè)計圖师倔,所以根據(jù)MVC的發(fā)展來看构韵,我們把MVP當(dāng)成MVC來看也不為過,因為MVP也是三層趋艘,唯一的差別是Model和View之間不進行通訊疲恢,都是通過Presenter完成。
前面介紹MVC的時候提到了算是致命缺點吧瓷胧,在android中由于activity(god object)的存在显拳,Controller和View很難做到完全解耦。但在MVP中就可以很好的解決這個問題
看下MVP的設(shè)計圖:


mvp.png

一般情況下就這兩種

2搓萧、MVP Sample

依然延續(xù)MVC的例子杂数,修改下結(jié)構(gòu)通過MVP去實現(xiàn),看下項目代碼結(jié)構(gòu):



callback瘸洛,http包下內(nèi)容基本一致揍移,主要看下不同的地方

public interface BasePresenter {
    void onDestroy();
}

BasePresenter類似于MVC中的BaseModel,主要負責(zé)業(yè)務(wù)邏輯的實現(xiàn)反肋。我們這里沒有把業(yè)務(wù)邏輯放在Model里去實現(xiàn)那伐,當(dāng)然把主要業(yè)務(wù)邏輯放在Model中去實現(xiàn)也是可以的。google的MVP實現(xiàn)方案是把業(yè)務(wù)邏輯放在presenter中石蔗,弱化Model罕邀,我們這里也是這樣做的。

public interface BaseView<P extends BasePresenter> {
    void setPresenter(P presenter);
}

BaseView是所有View的父類养距,將android中的view抽象話出來诉探,只有跟view相關(guān)的操作都由baseView的實現(xiàn)類去完成。

public class SampleContract {
    public static class Presenter implements BasePresenter
    {
        public void  getUserInfo(String uid,Callback1<SampleModel.UserInfo> callback)
        {
            SampleModel.UserInfo userInfo= new HttpUtil<SampleModel.UserInfo>().get(uid);
            callback.onCallBack(userInfo);
        }

        @Override
        public void onDestroy() {

        }
    }
    public interface View extends BaseView<Presenter>
    {
         void setDataToView(SampleModel.UserInfo userInfo);
    }
}

Contract 契約類這是Google MVP與其他實現(xiàn)方式的又一個不同棍厌,契約類用于定義同一個界面的view的接口和presenter的具體實現(xiàn)肾胯。好處是通過規(guī)范的方法命名和注釋可以清晰的看到整個頁面的邏輯竖席。

public class SampleActivity extends AppCompatActivity implements SampleContract.View{
    private  SampleContract.Presenter mPresenter;
    Button button;
    EditText textView;
    TextView tvAge,tvName;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sample);
        setPresenter(new SampleContract.Presenter());

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mPresenter.getUserInfo(textView.getText().toString(), new Callback1<SampleModel.UserInfo>() {
                    @Override
                    public void onCallBack(SampleModel.UserInfo userInfo) {
                        setDataToView(userInfo);
                    }
                });
            }
        });

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mPresenter.onDestroy();
    }

    @Override
    public void setDataToView(SampleModel.UserInfo userInfo) {
        tvAge.setText(userInfo.getAge());
        tvName.setText(userInfo.getName());
    }


    @Override
    public void setPresenter(SampleContract.Presenter presenter) {
        mPresenter=presenter;
    }
}

這里的SampleActivity實現(xiàn)了SampleContract.View只是作為View存在的。雖然看起來阳液,跟MVC中的實現(xiàn)很相似怕敬,但卻有本質(zhì)的區(qū)別。mPresenter為Model和View之間交互的橋梁帘皿。Presenter跟View相互持有东跪,這里SampleActivity實現(xiàn)了SampleContract.View,mPresenter作為SampleActivity的成員變量鹰溜,SampleActivity當(dāng)然持有mPresenter虽填,由于mPresenter是非靜態(tài)的成員標(biāo)量,因此默認持有SampleActivity的引用曹动。

3斋日、MVP總結(jié)

通過引入接口BaseView,讓相應(yīng)的視圖組件如Activity墓陈,F(xiàn)ragment去實現(xiàn)BaseView恶守,實現(xiàn)了視圖層的獨立,通過中間層Preseter實現(xiàn)了Model和View的完全解耦贡必。MVP徹底解決了MVC中View和Controller傻傻分不清楚的問題兔港,但是隨著業(yè)務(wù)邏輯的增加,一個頁面可能會非常復(fù)雜仔拟,UI的改變是非常多衫樊,會有非常多的case,這樣就會造成View的接口會很龐大利花。

MVVM

1科侈、MVVM說明

MVP中我們說過隨著業(yè)務(wù)邏輯的增加,UI的改變多的情況下炒事,會有非常多的跟UI相關(guān)的case臀栈,這樣就會造成View的接口會很龐大。而MVVM就解決了這個問題挠乳,通過雙向綁定的機制权薯,實現(xiàn)數(shù)據(jù)和UI內(nèi)容,只要想改其中一方欲侮,另一方都能夠及時更新的一種設(shè)計理念,這樣就省去了很多在View層中寫很多case的情況肋联,只需要改變數(shù)據(jù)就行威蕉。
先看下MVVM設(shè)計圖:


mvvm.png

一般情況下就這兩種情況,這看起來跟MVP好像沒啥差別橄仍,其實區(qū)別還是挺大的韧涨,在MVP中View和presenter要相互持有牍戚,方便調(diào)用對方,而在MVP中 View和ViewModel通過Binding進行關(guān)聯(lián)虑粥,他們之前的關(guān)聯(lián)處理通過DataBinding完成如孝。

2、MVVM與DataBinding的關(guān)系

一句話表述就是娩贷,MVVM是一種思想第晰,DataBinding是谷歌推出的方便實現(xiàn)MVVM的工具。在google推出DataBinding之前彬祖,因為xml layout功能較弱茁瘦,想實現(xiàn)MVVM非常困難。而DataBinding的出現(xiàn)可以讓我們很方便的實現(xiàn)MVVM储笑。

3甜熔、DataBinding簡介

DataBinding是實現(xiàn)視圖和數(shù)據(jù)雙向綁定的工具,這里簡單介紹下基本用法突倍,詳細用法可以參照官方:https://developer.android.com/topic/libraries/data-binding/
啟用DataBinding腔稀,只需要在gradle文件中添加如下代碼:

android {    
    dataBinding{
        enabled true
    }
}

通過DataBindingUtil可以動態(tài)生成一個ViewDataBinding的子類,類名以layout文件名大寫加Binding組成羽历,如:

ActivitySampleMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_sample_mvvm);

在layout中需要我們配置焊虏,每個控件綁定的實體對象,以layout進行包裹窄陡,data中配置變量名和類型炕淮,通過@{}或@={}的方式進行引用,其中@={}的方式表示雙向綁定跳夭。目前支持雙向綁定的控件如下:
AbsListView android:selectedItemPosition
CalendarView android:date
CompoundButton android:checked
DatePicker android:year, android:month, android:day
NumberPicker android:value
RadioGroup android:checkedButton
RatingBar android:rating
SeekBar android:progress
TabHost android:currentTab
TextView android:text
TimePicker android:hour, android:minute

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data >
        <variable
            name="user"
            type="com.androfarmer.mvvm.model.SampleModel.UserInfo">
        </variable>
    </data>
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@={user.name}"
        />
    <TextView
        android:id="@+id/tv_age"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@={user.age+``}"
        />

</LinearLayout>
</layout>

以上為具體在xml的用法展示

public static class UserInfo  extends BaseObservable
    {
        private int age;
        private String name;

        @Bindable
        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
            notifyPropertyChanged(BR.age);
        }

        @Bindable
        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
            notifyPropertyChanged(BR.name);
        }
    }

為了實現(xiàn)雙向綁定還需要對數(shù)據(jù)實體類做處理涂圆,繼承BaseObservable,對讀寫方法做@Bindable和notifyPropertyChanged處理币叹。還可以直接使用官方提供的泛型可觀察對象:ObservableField<T>
如:private ObservableField<String> name=new ObservableField<>();

4润歉、MVVM Sample

MVVM中跟MVP中一樣,將三層劃分的很清楚颈抚,Activity和xml layout充當(dāng)View踩衩,ViewModel處理業(yè)務(wù)邏輯以及獲取數(shù)據(jù),弱化Model贩汉。
很多代碼跟前面類似驱富,這里只列出核心代碼,ViewModel層的

public interface BaseViewModel {
    void onDestroy();
}

public abstract class AbstractViewModel<T extends ViewDataBinding> implements BaseViewModel {
    public T mViewDataBinding;
    public AbstractViewModel(T viewDataBinding)
    {
        this.mViewDataBinding=viewDataBinding;
    }

    @Override
    public void onDestroy() {
        mViewDataBinding.unbind();
    }
}

public class SampleViewModel extends AbstractViewModel<ActivitySampleMvvmBinding> {

    public SampleViewModel(ActivitySampleMvvmBinding viewDataBinding) {
        super(viewDataBinding);
    }

    public  void getUserInfo(String uid, Callback1<SampleModel.UserInfo> callback)
    {
        //從網(wǎng)絡(luò)或緩存獲取信息
        SampleModel.UserInfo userInfo=new SampleModel.UserInfo();
        userInfo.setName("tom");
        userInfo.setAge(18);
        callback.onCallBack(userInfo);
    }
}

ViewMode層主要處理業(yè)務(wù)邏輯和獲取數(shù)據(jù)匹舞,mViewDataBinding是通過View層傳遞過來褐鸥。

 private SampleViewModel mSampleViewModel;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivitySampleMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_sample_mvvm);
        mSampleViewModel=new SampleViewModel(binding);
        setDataToView();
    }
    private void setDataToView()
    {
        mSampleViewModel.getUserInfo("uid", new Callback1<SampleModel.UserInfo>() {
            @Override
            public void onCallBack(SampleModel.UserInfo userInfo) {
                mSampleViewModel.mViewDataBinding.setUser(userInfo);
            }
        });
    }

xml layout代碼在上面介紹databing的用法時已給出,通過上面代碼我們就將數(shù)據(jù)UserInfo跟View進行綁定了赐稽。比如我們更新用戶信息叫榕,
可以直接對View上的屬性進行修改: mSampleViewModel.mViewDataBinding.tvName.setText("rose");
也可以通過修改UserInfo實體類的字段信息:mSampleViewModel.mViewDataBinding.setUser(userInfo);
從此告別MVP中View層好多接口的問題浑侥,讓View變得更簡潔,修改任何一方晰绎,兩者都會保持?jǐn)?shù)據(jù)同步寓落。

5、MVVM 總結(jié)

看起來MVVM很好的解決了MVC和MVP的不足荞下,但是由于數(shù)據(jù)和視圖的雙向綁定伶选,導(dǎo)致出現(xiàn)問題時不太好定位來源,有可能數(shù)據(jù)問題導(dǎo)致锄弱,也有可能業(yè)務(wù)邏輯中對視圖屬性的修改導(dǎo)致考蕾。如果項目中打算用MVVM的話可以考慮使用官方的架構(gòu)組件ViewModel、LiveData会宪、DataBinding去實現(xiàn)MVVM

關(guān)于MVC,MVP,MVVM如何選擇的探討

前面在介紹MVC肖卧、MVP、MVVM時并沒有去詳細列出他們的優(yōu)缺點掸鹅,主要原因有兩個塞帐。
1、網(wǎng)上這方面總結(jié)的還是挺多的
2巍沙、其實關(guān)于架構(gòu)葵姥,設(shè)計,模塊化等等句携,它們的優(yōu)缺點沒有絕對的榔幸,主要看實現(xiàn)者如何去做,比如在mvp中我們要實現(xiàn)根據(jù)業(yè)務(wù)邏輯和頁面邏輯做很多Present和View的具體實現(xiàn)矮嫉,如果這些case太多削咆,會導(dǎo)致代碼的可讀性變差。但是通過引入contract契約類蠢笋,會讓業(yè)務(wù)邏輯變得清晰許多拨齐。因此不管是用哪種設(shè)計模式,只要運用得當(dāng)昨寞,都可以達到想要的結(jié)果瞻惋。
如果非要說怎么選的話,以我淺薄的知識建議如下:
1援岩、如果項目簡單歼狼,沒什么復(fù)雜性,未來改動也不大的話享怀,那就不要用設(shè)計模式或者架構(gòu)方法羽峰,只需要將每個模塊封裝好,方便調(diào)用即可凹蜈,不要為了使用設(shè)計模式或架構(gòu)方法而使用限寞。
2、對于偏向展示型的app仰坦,絕大多數(shù)業(yè)務(wù)邏輯都在后端履植,app主要功能就是展示數(shù)據(jù),交互等悄晃,建議使用mvvm玫霎。
3、對于工具類或者需要寫很多業(yè)務(wù)邏輯app妈橄,使用mvp或者mvvm都可庶近。
4、如果想通過一個項目去學(xué)習(xí)架構(gòu)和設(shè)計模式眷蚓,建議用MVC然后在此基礎(chǔ)上慢慢挖掘改進鼻种。最后你可能發(fā)現(xiàn),改進的最終結(jié)果可能就變成了mvp沙热,mvvm叉钥。

PS:
代碼部分很多只是為了演示具體設(shè)計模式原理,部分為偽代碼篙贸,還有些代碼寫的不是那么嚴(yán)謹(jǐn)投队。
本篇文章參考如下:
http://www.voidcn.com/article/p-ssodjasa-brk.html
http://www.reibang.com/p/4830912f5162

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市爵川,隨后出現(xiàn)的幾起案子敷鸦,更是在濱河造成了極大的恐慌,老刑警劉巖寝贡,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扒披,死亡現(xiàn)場離奇詭異,居然都是意外死亡兔甘,警方通過查閱死者的電腦和手機谎碍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來洞焙,“玉大人蟆淀,你說我怎么就攤上這事≡璺耍” “怎么了熔任?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長唁情。 經(jīng)常有香客問我疑苔,道長,這世上最難降的妖魔是什么甸鸟? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任惦费,我火速辦了婚禮兵迅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘薪贫。我一直安慰自己恍箭,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布瞧省。 她就那樣靜靜地躺著扯夭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鞍匾。 梳的紋絲不亂的頭發(fā)上交洗,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天,我揣著相機與錄音橡淑,去河邊找鬼构拳。 笑死,一個胖子當(dāng)著我的面吹牛梁棠,可吹牛的內(nèi)容都是我干的隐圾。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼掰茶,長吁一口氣:“原來是場噩夢啊……” “哼暇藏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起濒蒋,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤盐碱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后沪伙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瓮顽,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年围橡,在試婚紗的時候發(fā)現(xiàn)自己被綠了暖混。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡翁授,死狀恐怖拣播,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情收擦,我是刑警寧澤贮配,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站塞赂,受9級特大地震影響泪勒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一圆存、第九天 我趴在偏房一處隱蔽的房頂上張望叼旋。 院中可真熱鬧,春花似錦沦辙、人聲如沸送淆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辟拷,卻和暖如春撞羽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背衫冻。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工诀紊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人隅俘。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓邻奠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親为居。 傳聞我的和親對象是個殘疾皇子碌宴,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,515評論 2 359

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