An Absolute Beginner's Tutorial on Dependency Inversion Principle, Inversion of Control and Dependency Injection(關于依賴倒置原則,控制反轉(zhuǎn)和依賴注入的絕對的新手教程)

原文傳送門

翻譯方式: 中英文對照, 意譯

In this article we will talk about the Dependency Inversion Principle, Inversion of Control and Dependency Injection.
本文我們會談一下依賴倒置原則,反轉(zhuǎn)控制和依賴注入。

Introduction 介紹

In this article we will talk about the Dependency Inversion Principle, Inversion of Control and Dependency Injection.
本文我們會談一下依賴倒置原則,反轉(zhuǎn)控制和依賴注入。

We will start by looking at the dependency inversion principle. We will then see how we can use inversion of control to implement dependency inversion principle and finally we will look at what dependency injection is and how can it be implemented.
我們從依賴倒置原則開始,然后看下如何使用控制反轉(zhuǎn)來實現(xiàn)依賴倒置原則,最后我們看一下依賴注入是什么以及如何實現(xiàn)的。

Background 背景

Before we start talking about Dependency Injection(DI), we first need to understand the problem that DI solves.
在講述依賴注入之前, 我們首先需要了解依賴注入所解決的問題

To understand the problem, we need to know two things.
要明白問題, 我們需要知道兩個概念。

First dependency Inversion Principle(DIP) and second Inversion of Controls(IoC).
一是依賴倒置原則(DIP), 二是 控制反轉(zhuǎn)(IoC).

let us start our discussion with DIP, then we will talk about IoC.
我們首先談下 DIP, 然后是 IoC。

Once we have discussed these two, we will be in a better position to understand Dependency Injection, so we will look at dependency injection in details.
一旦我們明白了這兩個概念, 依賴注入就容易理解了, 因此我們詳細地看一下依賴注入。

Then finally we will discuss see how can we implement Dependency injection.
最后, 我們談下如何實現(xiàn)依賴注入。

Dependency Inversion Principle 依賴倒置原則

Dependency inversion principle is a software design principle which provides us the guidelines to write loosely coupled classes.
依賴倒置原則是一種軟件設計原則, 可以為我們寫松散耦合的類提供指引。

According to the definition of Dependency inversion principle:
根據(jù)依賴倒置原則的定義:

1、High-level modules should not depend on low-level modules. Both should depend on abstractions.
高層模塊不應該依賴于底層模塊。它們都應當依賴于抽象。

2、Abstractions should not depend upon details. Details should depend upon abstractions.
抽象不應當依賴于細節(jié)。細節(jié)應當依賴于抽象。

What does this definition mean? What is it trying to convey?
這個定義是什么意思? 它所要傳達的是什么?

let us try to understand the definition by looking at examples.
讓我們通過幾個例子來理解這個定義。

A few years back I was involved in writing a windows service which was supposed to run on a Web server.
幾年前, 我參與了編寫一項應當運行在 Web server 的 windows 服務。

The sole responsibility of this service was to log messages in event logs whenever there is some problem in the IIS application Pool.
該服務的唯一職責是: 無論何時, 只要 IIS 應用程序池出現(xiàn)問題, 就會在事件日志中記錄消息。

So what our team has done initially that we created two classes.
我們的團隊剛開始創(chuàng)建了2個類。

One for monitoring the Application Pool and second to write the messages in the event log.
一個用于監(jiān)控應用程序池, 另一個用于在事件日志中寫消息。

Our classes looked like this:
我們的類看起來是這樣子的:

class EventLogWriter
{
    public void Write(string message)
    {
        //Write to event log here
    }
}

class AppPoolWatcher
{
    // Handle to EventLog writer to write to the logs
    EventLogWriter writer = null;

    // 這個函數(shù)會在app pool 出現(xiàn)問題時調(diào)用
    public void Notify(string message)
    {
        if (writer == null)
        {
            writer = new EventLogWriter();
        }
        writer.Write(message);
    }
}

From the first look, the above class design seems to be sufficient.It looks perfectly good code.
一眼看去, 上面的設計的類看起來已經(jīng)足夠了??瓷先ナ呛芎玫拇a。

But there is a problem in the above design.
但是上面的這個設計有個問題。

This design violates the dependency inversion principle. i.e. the high level module <code>AppPoolWatcher depends on EventLogWriter which is a concrete class and not an abstraction.
這個設計破壞了依賴倒置原則。也就是說, 高層模塊 AppPoolWatcher 依賴于 EventLogWriter, 而 EventLogWriter 是個具體的類并不是抽象

How is it a problem?
這有什么問題呢?

Well let me tell you the next requirement we received for this service and the problem will become very clearly visible.
好吧, 當我告訴你我們接收到的這個服務的下一個需求后, 這個問題就會顯而易見。

The next requirement we received for this service was to send email to network administrator's email ID for some specific set of error.
我們所接受的下一個需求是: 當發(fā)生一些特定的錯誤,向網(wǎng)絡管理員的電子郵件ID 發(fā)送郵件。

Now, how will we do that?
現(xiàn)在, 我們怎么辦呢?

One idea is to create a class for sending emails and keeping its handle in the AppPoolWatcher but at any moment we will be using only one object either EventLogWriter or EmailSender.
一個想法是創(chuàng)建一個類用于發(fā)送郵件并把它的句柄放在 AppPoolWatcher 中,但是任何時刻, 我們只能用一個對象, 不是 EventLogWriter 就是 EmailSender

The problem will get even worse when we have more actions to take selectively, like sending SMS.
當我們有選擇地采取更多動作,比如發(fā)送短信,問題就會變得更糟。

Then we will have to have one more class whose instance will be kept inside the AppPoolWatcher.
我們將不得不再添加一個類, 類的實例保存在 AppPoolWatcher 中。

The dependency inversion principle says that we need to decouple this system in such a way that the higher level modules i.e. the AppPoolWatcher in our case will depend on a simple abstraction and will use it.
依據(jù)依賴倒置原則, 我們需要將這個系統(tǒng)解耦,高層模塊(即 AppPoolWatcher) 應當依賴于簡單的抽象并使用它。

This abstraction will in turn will be mapped to some concrete class which will perform the actual operation. (Next we will see how this can be done)
該抽象會映射到一些具體的類, 由這些類進行真正的操作.(稍后我們會看到這是如何做到的)

Inversion of Control 控制反轉(zhuǎn)

Dependency inversion was a software design principle, it just states that how two modules should depend on each other.
依賴倒置是一種軟件設計原則,它只是說明兩個模塊之間應該如何相互依賴。

Now the question comes, how exactly we are going to do it?
現(xiàn)在問題來了,我們到底要怎么做呢?

The answer is Inversion of control.
答案是控制反轉(zhuǎn)。

Inversion of control is the actual mechanism using which we can make the higher level modules to depend on abstractions rather than concrete implementation of lower level modules.
使用控制反轉(zhuǎn)機制, 我們可以讓高層模塊依賴于抽象而不是底層模塊的具體實現(xiàn)。

So if I have to implement inversion of control in the above mentioned problem scenario, the first thing we need to do is to create an abstraction that the higher levels will depend on.
因此, 如果必須在上述問題場景中實現(xiàn)控制反轉(zhuǎn),我們需要做的第一件事就是創(chuàng)建高層模塊所依賴的抽象。

So let us create an interface that will provide the abstraction to act on the notification received from AppPoolWacther.
因此, 我們創(chuàng)建一個接口, 這個接口會提供對 AppPoolWatcher 所發(fā)送的通知的操作的抽象。

public interface INofificationAction
{
    public void ActOnNotification(string message);
}

Now let us change our higher level module i.e. the AppPoolWatcher to use this abstraction rather than the lower level concrete class.
現(xiàn)在, 我們修改下高層模塊,即 AppPoolWatcher, 使用這個抽象, 而不是底層的具體類。

class AppPoolWatcher
{
    // Handle to EventLog writer to write to the logs 
    // EventLogWriter 寫日志的句柄
    INofificationAction action = null;

    // 這個函數(shù)會在app pool 出現(xiàn)問題時調(diào)用
    public void Notify(string message)
    {
        if (action == null)
        {
            // Here we will map the abstraction i.e. interface to concrete class
            // 這里 我們會將抽象(即接口)映射到具體的類
        }
        action.ActOnNotification(message);
    }
}

So how will our lower level concrete class will change? how will this class conform to the abstraction i.e. we need to implement the above interface in this class:
那么,我們的底層具體類將如何改變呢?這個類如何符合抽象,即我們需要在這個類中實現(xiàn)上述接口:

class EventLogWriter : INofificationAction
{   
    public void ActOnNotification(string message)
    {
        // Write to event log here
    }
}

So now if I need to have the concrete classes for sending email and sms, these classes will also implement the same interface.
這樣,如果我需要有發(fā)送電子郵件和短信的具體類,這些類也將實現(xiàn)相同的接口。

class EmailSender : INofificationAction
{
    public void ActOnNotification(string message)
    {
        // Send email from here
    }
}

class SMSSender : INofificationAction
{
    public void ActOnNotification(string message)
    {
        // Send SMS from here
    }
}

So the final class design will look like:
那么, 最終的類設計看起來會是這樣子的:


So what we have done here is that, we have inverted the control to conform to dependency inversion principle.
我們在這里所做的是, 為和依賴倒置原則相一致, 我們反轉(zhuǎn)了控制。

Now our high level modules are dependent only on abstractions and not the lower level concrete implementations, which is exactly what dependency inversion principle states.
現(xiàn)在我們的高層模塊只依賴于抽象而不依賴于底層的具體實現(xiàn),這正是依賴倒置原則所陳述的。

But there is still one missing piece.
但是仍然有一塊缺失。

When we look at the code of our AppPoolWatcher, we can see that it is using the abstraction i.e. interface but where exactly are we creating the concrete type and assigning it to this abstraction.
當我們看一下我們的 AppPoolWatcher 代碼, 我們可以看到它使用了抽象,也就是接口, 但是我們在哪里創(chuàng)建具體的類,并把它分配給這個抽象呢。

To solve this problem, we can do something like:
為了解決這個問題, 我們可以這樣做:

class AppPoolWatcher
{
    // Handle to EventLog writer to write to the logs
    INofificationAction action = null;

    // This function will be called when the app pool has problem
    public void Notify(string message)
    {
        if (action == null)
        {
            // Here we will map the abstraction i.e. interface to concrete class 
            writer = new EventLogWriter();
        }
        action.ActOnNotification(message);
    }
}

But we are again back to where we have started.
但是我們又一次回到了起點。

The concrete class creation is still inside the higher level class.
具體類的創(chuàng)建仍然在高層類中。

Can we not make it totally decoupled so that even if we add new classes derived from INotificationAction, we don't have to change this class.
我們能不能讓它完全解耦, 這樣即使我們添加了從INotificationAction 派生出的新類, 我們也不用改變這個類。

This is exactly where Dependency injection comes in picture.
這正是依賴注入的所在。

So its time to look at dependency injection in detail now.
現(xiàn)在是時候去詳細地看一下依賴注入了。

Dependency Injection 依賴注入

Now that we know the dependency inversion principle and have seen the inversion of control methodology for implementing the dependency inversion principle, Dependency Injection is mainly for injecting the concrete implementation into a class that is using abstraction i.e. interface inside.
現(xiàn)在我們已經(jīng)了解了依賴倒置原則,并且已經(jīng)看到了為實現(xiàn)依賴倒置原則而進行的控制方法的反轉(zhuǎn),依賴注入主要是將具體實現(xiàn)注入到一個使用抽象的類中,即接口內(nèi)部。

The main idea of dependency injection is to reduce the coupling between classes and move the binding of abstraction and concrete implementation out of the dependent class.
依賴注入的主要思想是減少類之間的耦合,將抽象和具體實現(xiàn)的綁定移出依賴類。

Dependency injection can be done in three ways.
依賴注入可以用三種方式實現(xiàn)。

1、Constructor injection
構造器注入

2、Method injection
方法注入

3、Property injection
屬性注入

Constructor Injection 構造器注入

In this approach we pass the object of the concrete class into the constructor of the dependent class.
在這種方式中,我們將具體類的對象傳給依賴類的構造函數(shù)

So what we need to do to implement this is to have a constructor in the dependent class that will take the concrete class object and assign it to the interface handle this class is using.
因此,我們需要做的是在依賴類中放一個構造函數(shù),它將使用具體的類對象并將其賦值給這個類使用的接口。

So if we need to implement this for our AppPoolWatcher class:
因此, 如果我們需要為我們的 AppPoolWatcher類實現(xiàn)這種方式:

class AppPoolWatcher
{
    // Handle to EventLog writer to write to the logs
    INofificationAction action = null;

    public AppPoolWatcher(INofificationAction concreteImplementation)
    {
        this.action = concreteImplementation;
    }

    // This function will be called when the app pool has problem
    public void Notify(string message)
    {   
        action.ActOnNotification(message);
    }
}

In the above code, the constructor will take the concrete class object and bind it to the interface handle.
上述代碼中, 構造器將使用具體類對象,并把它綁定到接口句柄。

So if we need to pass the EventLogWriter's concrete implementation into this class, all we need to do is
如果我們要把 EventLogWriter 的具體實現(xiàn)傳入這個類中, 我們所要做的是

EventLogWriter writer = new EventLogWriter();
AppPoolWatcher watcher = new AppPoolWatcher(writer);
watcher.Notify("Sample message to log");

Now if we want this class to send email or sms instead, all we need to do is to pass the object of the respective class in the AppPoolWatcher's constructor.
現(xiàn)在, 如果我們想讓這個類發(fā)送郵件或者是短信, 我們要做的就是把各自類的對象傳入到 AppPoolWatcher 的構造器中。

This method is useful when we know that the instance of the dependent class will use the same concrete class for its entire lifetime.
這個方法非常有用, 當我們知道依賴類的實例將在其整個生命周期中會使用相同的具體類。

Method Injection 方法注入

In constructor injection we saw that the dependent class will use the same concrete class for its entire lifetime.
在構造器注入中, 我們可以看到依賴類會在其整個生命周期使用相同的具體類。

Now if we need to pass separate concrete class on each invocation of the method, we have to pass the dependency in the method only.
現(xiàn)在,如果我們需要在方法的每次調(diào)用中傳入單獨的具體類,我們必須只在方法中的傳遞依賴。

So in method injection approach we pass the object of the concrete class into the method the dependent class which is actually invoking the action.
因此, 在方法注入方式中, 我們傳遞 具體類的對象到 事實上正調(diào)用操作的依賴類的方法中。

So what we need to do to implement this is to have the action function also accept an argument for the concrete class object and assign it to the interface handle this class is using and invoke the action.
因此, 要實現(xiàn)這種方式,我們所要做的就是讓動作函數(shù)也接收具體類對象參數(shù), 并把它賦值給這個類正使用的句柄,然后調(diào)用該操作。

So if we need to implement this for our AppPoolWatcher class:
因此, 我們需要為我們的 AppPoolWatcher 類實現(xiàn)這種方式:

class AppPoolWatcher
{
    // Handle to EventLog writer to write to the logs
    INofificationAction action = null;

    // This function will be called when the app pool has problem
    public void Notify(INofificationAction concreteAction, string message)
    {
        this.action = concreteAction;
        action.ActOnNotification(message);
    }
}

In the above code the action method i.e. Notify will take the concrete class object and bind it to the interface handle.
上述代碼中 動作方法 即 Notify 會使用具體類對象并且把它綁定到接口句柄。

So if we need to pass the EventLogWriter's concrete implementation into this class, all we need to do is
所以, 如果我們需要傳遞 EventLogWriter的具體實現(xiàn)到這個類中, 我們要做的是:

EventLogWriter writer = new EventLogWriter();
AppPoolWatcher watcher = new AppPoolWatcher();
watcher.Notify(writer, "Sample message to log");

Now if we want this class to send email or sms instead, all we need to do is to pass the object of the respective class in the AppPoolWatcher's invocation method i.e. Notify method in the above example.
現(xiàn)在, 如果我們要讓這個類發(fā)送郵件或者是短信, 我們要做的就是把各自類的對象傳入到 AppPoolWatcher 的調(diào)用方法, 即 上例中的 Notify 方法。

Property Injection 屬性注入

Now we have discussed two scenarios where in constructor injection we knew that the dependent class will use one concrete class for the entire lifetime.
現(xiàn)在我們已經(jīng)討論了兩種場景, 在構造器注入中我們了解了依賴類在整個生命期間會使用一個具體類。

The second approach is to use the method injection where we can pass the concrete class object in the action method itself.
第二種方法是使用方法注入, 我們可以把具體類的對象傳入到動作方法。

But what if the responsibility of selection of concrete class and invocation of method are in separate places.
但是如果具體類的選擇以及方法調(diào)用的責任在不同的地方會怎樣?

In such cases we need property injection.
這種情況下我們需要屬性注入。

So in this approach we pass the object of the concrete class via a setter property that was exposed by the dependent class.
在這種方法中, 我們通過依賴類所暴露的 setter 屬性 傳入具體類的對象。

So what we need to do to implement this is to have a Setter property or function in the dependent class that will take the concrete class object and assign it to the interface handle this class is using.
要實現(xiàn)這種方法, 我們需要做的是 在依賴類中添加 Setter 屬性或者是函數(shù), 它會將具體類的對象賦值給這個類使用的句柄。

So if we need to implement this for our AppPoolWatcher class:
因此, 如果我們需要為我們的 AppPoolWatcher 類實現(xiàn)這個方法:

class AppPoolWatcher
{
    // Handle to EventLog writer to write to the logs
    INofificationAction action = null;

    public INofificationAction Action
    {
        get
        {
            return action;
        }
        set
        {
            action = value;
        }
    }

    // This function will be called when the app pool has problem
    public void Notify(string message)
    {   
        action.ActOnNotification(message);
    }
}

In the above code the setter of Action property will take the concrete class object and bind it to the interface handle.
上述代碼中, Action 屬性會使用具體類的對象并把它綁定到接口句柄。

So if we need to pass the EventLogWriter's concrete implementation into this class, all we need to do is
所以, 如果我們把 EventLogWriter 的具體實現(xiàn)傳入到這個類中, 我們要做的就是:

EventLogWriter writer = new EventLogWriter();
AppPoolWatcher watcher = new AppPoolWatcher();
// This can be done in some class
watcher.Action = writer;

// This can be done in some other class
watcher.Notify("Sample message to log");

Now if we want this class to send email or sms instead, all we need to do is to pass the object of the respective class in the setter exposed by AppPoolWatcher class.
現(xiàn)在, 如果我們想要這個類發(fā)送郵件或者是短信, 我們所要做的就是把相應類的對象 傳入到AppPoolWatcher類暴露的setter

This approach is useful when the responsibility of selecting the concrete implementation and invoking the action are done in separate places/modules.
當選擇具體實現(xiàn)和調(diào)用操作的責任在不同的地方/模塊中完成時,這種方法非常有用。

In languages where properties are not supported, there is a separate function to set the dependency.
在不支持屬性的語言中, 有單獨的函數(shù)設置依賴。

This approach is also known as setter injection.
這種方法也成為 setter 注入。

The important thing to note in this approach is that there is a possibility that someone has created the dependent class but no one has set the concrete class dependency yet.
這種方法中需要注意的是, 有可能已經(jīng)創(chuàng)建了依賴類但是沒有設置具體類的依賴。

If we try to invoke the action in such cases then we should have either some default dependency mapped to the dependent class or have some mechanism to ensure that application will behave properly.
如果我們試圖在這種情況下調(diào)用該操作, 那么我們應該將默認依賴映射到依賴類, 或者有某種機制可以確保應用程序可以正常運行。

A Note on IoC Containers 關于 IoC 容器的說明

Constructor injection is the mostly used approach when it comes to implementing the dependency injection.
在實現(xiàn)依賴注入時, 構造器注入是最常用的方法

If we need to pass different dependencies on every method call then we use method injection.
如果我們需要在每次方法調(diào)用的時候傳遞不同的依賴, 那么我們需要方法注入。

Property injection is used less frequently.
屬性注入的使用頻率較低。

All the three approaches we have discussed for dependency injection are ok if we have only one level of dependency.
如果我們只有一個依賴級別, 我們所談到的依賴注入的這三種方法都是可以的。

But what if the concrete classes are also dependent of some other abstractions.
但是如果具體的類也是其他抽象的依賴該怎么辦呢

So if we have chained and nested dependencies, implementing dependency injection will become quite complicated.
那么, 如果我們有鏈式的以及嵌套的依賴, 實現(xiàn)依賴注入將會變得非常復雜。

That is where we can use IoC containers.
這時, 我們就可以使用 IoC 容器。

IoC containers will help us to map the dependencies easily when we have chained or nested dependencies.
當我們使用鏈式的或嵌套依賴時,IoC 容器會幫助我們輕松地映射依賴關系。

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

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