Kotlin知識歸納(十) —— 委托

前序

??????委托琴许,對于很多Java開發(fā)者來說都會一面蒙蔽,我也不例外溉躲。委托榜田,維基百科的解釋是:有兩個對象參與處理同一個請求,接受請求的對象將請求委托給另一個對象來處理签财。這好像有一點代理的味道(*゜ー゜*)串慰。Kotlin中委托分為類委托委托屬性

類委托

??????在解釋類委托之前唱蒸,需要先了解一波裝飾設計模式邦鲫。裝飾設計模式的核心思想是:

不使用繼承的情況下,擴展一個對象的功能神汹,使該對象變得更加強大庆捺。

??????通常套路是:創(chuàng)建一個新類,新類實現(xiàn)與原始類一樣的接口屁魏,并將原來的類的實例作為作為一個字段保存滔以,與原始類擁有同樣的行為(方法)。一部分行為(方法)與原始類保持一致(即直接調(diào)用原始類的行為(方法))氓拼,還有一部分行為(方法)在原始類的行為(方法)基礎上進行擴展你画。

??????裝飾設計模式的缺點是需要較多的樣板代碼,顯得比較啰嗦桃漾。例如:最原始的裝飾類需要實現(xiàn)接口的全部方法坏匪,并在這些方法中調(diào)用原始類對象對應的方法。

class CustomList<T>(
    val innerList:MutableList<T> = ArrayList<T>()):MutableCollection<T> {
    
    override val size: Int = innerList.size
    override fun contains(element: T): Boolean  = innerList.contains(element)
    override fun containsAll(elements: Collection<T>): Boolean = innerList.containsAll(elements)
    override fun isEmpty(): Boolean  = innerList.isEmpty()
    override fun add(element: T): Boolean  = innerList.add(element)
    override fun addAll(elements: Collection<T>): Boolean  = innerList.addAll(elements)
    override fun clear()  = innerList.clear()
    override fun iterator(): MutableIterator<T>  = innerList.iterator()
    override fun remove(element: T): Boolean  = innerList.remove(element)
    override fun removeAll(elements: Collection<T>): Boolean  = innerList.removeAll(elements)
    override fun retainAll(elements: Collection<T>): Boolean  = innerList.retainAll(elements)
    
}

??????但Kotlin將委托作為一個語言級別的功能進行頭等支持撬统∈首遥可以利用by關鍵字,將新類的接口實現(xiàn)委托給原始類恋追,編譯器會為新類自動生成接口方法凭迹,并默認返回原始類對應的具體實現(xiàn)罚屋。然后我們重載需要擴展的方法。

class CustomList<T>(
    val innerList:MutableList<T> = ArrayList<T>()):MutableCollection<T> by innerList{
    
    override fun add(element: T): Boolean {
        println("CustomList add element")
        innerList.add(element)
    }

}

委托屬性

委托屬性就是將屬性的訪問器(getset)委托給一個符合屬性委托約定規(guī)則的對象的嗅绸。

??????委托屬性和類委托不同脾猛,委托屬性更像是給屬性找代理。 委托屬性同樣是利用by關鍵字鱼鸠,將屬性委托給代理對象尖滚。屬性的代理對象不必實現(xiàn)任何的接口,但是需要提供一個 getValue() 函數(shù)與 setValue()函數(shù)(僅限 var 屬性)瞧柔。例如:

class Person{
    var name:String by Delegate()
}

class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "kotlin"
    }
 
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String){
        
    }
}

??????屬性 name 將自己的set/get方法委托給了Delegate對象的getValue()setValue()漆弄。在getValue()setValue()中都有operator修飾,意味著委托屬性也是依賴于約定的功能造锅。像其他約定的函數(shù)一樣撼唾,getValue()setValue() 可以是成員函數(shù),也可以是擴展函數(shù)哥蔚。

??????Kotlin官方庫中提供 ReadOnlyPropertyReadWriteProperty 接口倒谷,方便開發(fā)者實現(xiàn)這些接口來提供正確getValue()方法 和 setValue()方法。

public interface ReadOnlyProperty<in R, out T> {
    public operator fun getValue(thisRef: R, property: KProperty<*>): T
}

public interface ReadWriteProperty<in R, T> {
    public operator fun getValue(thisRef: R, property: KProperty<*>): T
    public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}

使用委托屬性

惰性初始化

??????當需要進行屬性延遲初始化時糙箍,往往會想到使用lateinit var進行延遲初始化渤愁。但那是對于var變量,即可變變量深夯,但對于val變量呢抖格?可以使用支持屬性來實現(xiàn)惰性初始化:

class Person{
    //真正存儲郵箱列表的對象
    private var _emails:List<Email>? = null
    
    //對外暴露的郵箱列表對象
    val emails:List<Email>
        get() {
            if ( _emails == null){
                _emails = ArrayList<Email>()
            }
            return _emails!!
        }
}

??????提供一個"隱藏"屬性_emails用來存儲真正的值,而另一個屬性emails用來提供屬性的讀取訪問咕晋。_emails是可變可空雹拄,emails不可變不可空,當你訪問emails時掌呜,才初始化_emails變量滓玖,并返回_emails對象,達到對val對象延遲初始化的目的质蕉。

??????但這種方案在需要多個惰性屬性時势篡,就顯得很啰嗦了,而且他并不是線程安全的模暗。Kotlin提供了更加便捷的解決方案:委托屬性禁悠,并使用標準庫函數(shù)lazy返回代理對象。

class Person{
    val email:List<Email> by lazy {
        ArrayList<Email>()
    }
}

??????lazy函數(shù)接收初始化該值操作的lambda汰蓉,并返回一個具有getValue()方法的代理對象绷蹲,并配合by關鍵字將屬性委托給lazy函數(shù)返回的代理對象棒卷。lazy函數(shù)是線程安全的顾孽,不用擔心異步的問題祝钢。

屬性改變的通知

??????當一個對象的屬性需要更改時得到通知,最原始的辦法就是若厚,重寫set方法拦英,在set方法中設置處理屬性改變的邏輯。手工實現(xiàn)屬性修改的通知:

class Person(name:String,age:Int){
    var age :Int = age
        set(newValue) {
            val oldValue = field
            field = newValue
            //監(jiān)聽值改變(或使用Listener對象)
            valueChangeListener("age",oldValue,newValue)
        }
    var name :String = name
        set(newValue) {
            val oldValue = field
            field = newValue
            //監(jiān)聽值改變
            valueChangeListener("name",oldValue,newValue)
        }

    fun <T> valueChangeListener(fieldName:String,oldValue:T,newValue:T){
        println("$fieldName oldValue = $oldValue newValue = $newValue")
    }
}

??????但這種方案在跟惰性初始化最開始的例子類似测秸,當需要監(jiān)聽多個屬性時疤估,代碼冗長且啰嗦。我們可以像惰性初始化一樣霎冯,使用委托屬性實現(xiàn):

class Person(name:String,age:Int){
    var age:Int by PropertyObservable(age){  property, oldValue, newValue ->

    }
    var name:String by PropertyObservable(name){ property, oldValue, newValue ->

    }
}

//委托類
 class PropertyObservable<T>(var initValue:T,
                             val observer:(property: KProperty<*>, oldValue: T, newValue: T) -> Unit): ReadWriteProperty<Person, T> {
    override fun getValue(thisRef: Person, property: KProperty<*>): T {
        return initValue;
    }

    override fun setValue(thisRef: Person, property: KProperty<*>, newValue: T) {
        val oldeValue = initValue
        initValue = newValue
        //監(jiān)聽值改變(或使用Listener對象)
        observer(property,oldeValue,newValue)
    }
}

??????定義委托類铃拇,通過委托屬性"接管"該屬性的get/set,并提供初始值沈撞,以及屬性被修改時的處理邏輯慷荔。大大簡化屬性設置監(jiān)聽的代碼。

??????但Kotlin在標準庫中已經(jīng)為我們提供了Delegates.observable()方法,大大方便我們使用委托屬性對屬性的修改進行監(jiān)聽缠俺,像我們自定義的委托類一樣显晶,該方法接受屬性的初始化值,以及屬性變化時的處理邏輯:

class Person(name:String,age:Int){
    var age:Int by Delegates.observable(age){ property, oldValue, newValue ->  
        println("${property.name} oldValue = $oldValue newValue = $newValue")
    }
    var name:String by Delegates.observable(name){ property, oldValue, newValue ->
         println("${property.name} oldValue = $oldValue newValue = $newValue")
    }
}

??????Kotlin在標準庫中提供了一個類似Delegates.observable()的方法:Delegates.vetoable()壹士。但會在屬性被賦新值生效之前會傳遞給 Delegates.vetoable() 進行處理磷雇,依據(jù)Delegates.vetoable()的返回的布爾值判斷要不要賦新值。

第三種延遲初始化

??????之前已經(jīng)知道躏救,var屬性需要延遲初始化時唯笙,可以使用lateinit關鍵字,val屬性需要延遲初始化時盒使,可以使用委托屬性 + lazy()函數(shù)的方法。但lateinit關鍵字的延遲處理僅對引用類型有用呢堰,對基本數(shù)據(jù)類型無效枉疼,當需要對基本數(shù)據(jù)類型進行延遲初始化怎么辦呢鞋拟?Kotlin通過委托屬性提供另一種延遲初始化的方式:Delegates.notNull()

var num:Int by Delegates.notNull()

??????雖然Kotlin提供了延遲初始化的方式航闺,使開發(fā)者不用強制在構造函數(shù)中初始化(例如Activity中在onCreate中初始化),但對于延遲初始化的值侮措,必須確保其被初始化分扎,否則將會像Java空指針一樣畏吓,拋出異常菲饼。

方式 適用類型
lateinit 引用類型
Delegates.notNull() 基本數(shù)據(jù)類型巴粪、引用類型

轉(zhuǎn)換規(guī)則

??????每個委托屬性的實現(xiàn)的背后肛根,Kotlin 編譯器都會生成輔助屬性并委托給它派哲。例如:對于屬性 name,編譯器會生成隱藏屬性 name$delegate感耙,而屬性 name訪問器的代碼委托給隱藏屬性的getValue()/setValue()即硼。

class Person{
    var name:String by MyDelegate()
}

編譯器生成以下代碼:

class Person{
    private val name$delegate = MyDelegate()
    
    var name:String
        get() = name$delegate.getValue(this,<property>)
        set(value:String) = name$delegate.setValue(this,<property>,value)
}
  • thisRef表示持有該委托屬性的對象
  • property KProperty<*> 類型或是它的父類褥实,屬性的描述裂允。(可獲取屬性的名稱等)
  • value 屬性的新值

源碼閱讀

??????掌握了Kotllin的委托屬性如何使用后僻澎,還需要深入了解下委托屬性的源碼:

image

NotNullVar:Delegates.notNull()延遲初始化的委托類

Delegates:Delegates作為一個對象聲明存在祖乳,里面擁有3個非常熟悉的方法:notNull()拳恋、observablevetoable谬运。

ObservablePropertyObservableProperty系統(tǒng)定義的委托類梆暖,observablevetoable返回該委托類的匿名對象轰驳。

Delegates.notNull()

??????Delegates.notNull()直接返回NotNullVar對象作為委托屬性的代理對象级解。

#Delegates.kt
public fun <T : Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar()
#Delegates.kt
private class NotNullVar<T : Any>() : ReadWriteProperty<Any?, T> {
    private var value: T? = null

    public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
    }

    public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        this.value = value
    }
}

??????從源碼中可以看到其內(nèi)部實現(xiàn)與之前我們使用的支持屬性是一樣的原理勤哗,但他提供了getValue()setValue(),使Delegates.notNull()可以代理var屬性芒划。

Delegates.observable()

??????Delegates.observable()Delegates.vetoable()一樣,都是直接返回ObservableProperty的匿名對象拼苍。但Delegates.observable()重載afterChange函數(shù)调缨,并在afterChange函數(shù)中執(zhí)行Delegates.observable()接收的lambda棚点。ObservableProperty#setValue()在對屬性賦新值后,將舊值和新值作為參數(shù)執(zhí)行afterChange函數(shù)瘫析。

#Delegates.kt
public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
            ReadWriteProperty<Any?, T> =
        object : ObservableProperty<T>(initialValue) {
            override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
        }
#ObservableProperty.kt
protected open fun afterChange(property: KProperty<*>, oldValue: T, newValue: T): Unit {}

public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        val oldValue = this.value
        if (!beforeChange(property, oldValue, value)) {
            return
        }
        this.value = value
        afterChange(property, oldValue, value)
    }

Delegates.vetoable()

??????Delegates.vetoable()Delegates.observable()非常相似咸包,只是重載的函數(shù)不一致烂瘫,Delegates.vetoable()重載beforeChange函數(shù)。ObservablePropertygetValue()會先獲取beforeChange函數(shù)的返回值(默認是true)葛账,判斷是否繼續(xù)執(zhí)行賦值操作籍琳。所以這就是Delegates.vetoable()的不同的地方趋急。

#Delegates.kt
public inline fun <T> vetoable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean):
            ReadWriteProperty<Any?, T> =
        object : ObservableProperty<T>(initialValue) {
            override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = onChange(property, oldValue, newValue)
        }
#ObservableProperty.kt
protected open fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = true

public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        val oldValue = this.value
        //如果beforeChange返回false宣谈,則直接返回函數(shù),不賦值
        if (!beforeChange(property, oldValue, value)) {
            return
        }
        this.value = value
        afterChange(property, oldValue, value)
    }

委托屬性的原理

??????想要更深層次的了解Kotlin的委托嗦嗡,最好的辦法就是將其轉(zhuǎn)換成Java代碼進行研究侥祭。

#daqiKotlin.kt
class Person{
    var name:String by Delegates.observable("daqi"){ property, oldValue, newValue ->
        println("${property.name}  oldValue = $oldValue  newValue = $newValue")
    }
}

反編譯后的Java代碼:

public final class Person$$special$$inlined$observable$1 extends ObservableProperty {
   // $FF: synthetic field
   final Object $initialValue;

   public Person$$special$$inlined$observable$1(Object $captured_local_variable$1, Object $super_call_param$2) {
      super($super_call_param$2);
      this.$initialValue = $captured_local_variable$1;
   }

   protected void afterChange(@NotNull KProperty property, Object oldValue, Object newValue) {
      Intrinsics.checkParameterIsNotNull(property, "property");
      String newValue = (String)newValue;
      String oldValue = (String)oldValue;
      int var7 = false;
      String var8 = property.getName() + "  oldValue = " + oldValue + "  newValue = " + newValue;
      System.out.println(var8);
   }
}

public final class Person {
   // $FF: synthetic field
   static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Person.class), "name", "getName()Ljava/lang/String;"))};
   @NotNull
   private final ReadWriteProperty name$delegate;

   @NotNull
   public final String getName() {
      return (String)this.name$delegate.getValue(this, $$delegatedProperties[0]);
   }

   public final void setName(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.name$delegate.setValue(this, $$delegatedProperties[0], var1);
   }

   public Person() {
      Delegates var1 = Delegates.INSTANCE;
      Object initialValue$iv = "daqi";
      ReadWriteProperty var5 = (ReadWriteProperty)(new Person$$special$$inlined$observable$1(initialValue$iv, initialValue$iv));
      this.name$delegate = var5;
   }
}
  • 1次哈、創(chuàng)建一個繼承自ObservablePropertyPerson$$special$$inlined$observable$1類琼牧,因為Delegates.observable()是返回一個匿名的ObservableProperty對象。
  • 2撬槽、Person類中定義了一個name$delegate屬性,該屬性指向name屬性的代理對象暂题,即Person$$special$$inlined$observable$1類的對象敢靡。
  • 3、Person類中name屬性會轉(zhuǎn)換為getName()setName()赶站。
  • 4想括、name屬性的getset方法的內(nèi)部調(diào)用name$delegate相應的setValue()getValue()瑟蜈。
  • 5铺根、KProperty數(shù)組中會保存通過Kotlin反射得到的Personr類中的name屬性的信息。在調(diào)用name$delegatesetValue()getValue()時掂林,將這些信息作為參數(shù)傳遞進去泻帮。

幕后字段與幕后屬性

??????你看完反編譯的Java源碼后刑顺,或許會發(fā)現(xiàn)一個問題:為什么Kotlin中Personname屬性并沒有在Java的Person中被定義狼讨,只實現(xiàn)了該屬性的getset方法。

??????這其中涉及到Kotlin的幕后字段的問題布隔, Kotlin 什么是幕后字段衅檀? 中講得很清楚:

只有擁有幕后字段的屬性轉(zhuǎn)換成Java代碼時霎俩,才有對應的Java變量哀军。

Kotlin屬性擁有幕后字段需要滿足以下條件之一:

  • 使用默認 getter / setter 的屬性,一定有幕后字段打却。對于 var 屬性來說杉适,只要 getter / setter 中有一個使用默認實現(xiàn),就會生成幕后字段;
  • 在自定義 getter / setter 中使用了 field 的屬性

??????所以也就能理解柳击,為什么擴展屬性不能使用 field猿推,因為擴展屬性并不能真的在該類中添加新的屬性,不能具有幕后字段捌肴。而且委托屬性中蹬叭,該屬性的getset方法內(nèi)部都是調(diào)用代理對象的getValue()setValue(),并沒有使用 field ,且都不是使用默認的getset方法。

總結(jié)

  • 類委托可以很方便的實現(xiàn)裝飾設計模式铺坞,開發(fā)者只用關心需要擴展的方法。
  • 委托屬性就是將該屬性的setget交由 代理對象 的setValuegetValue來處理叉弦。
  • 委托屬性也是一種 約定 樱拴。setValuegetValue都需帶有operator關鍵字修飾正罢。
  • Kotlin標準庫提供 ReadOnlyPropertyReadWriteProperty 接口呛占,方便開發(fā)者實現(xiàn)這些接口來提供正確getValue()方法 和 setValue()方法。
  • val屬性可以借助 委托屬性 進行延遲初始化洪灯,使用lazy()設置初始化流程铅檩,并自動返回代理對象。
  • Delegates.observable()能在 被委托的屬性 改變時接收到通知,有點類似ACC的LiveData
  • Delegates.vetoable()能在 被委托的屬性 改變前接收通知,并能決定該屬性賦不賦予新值。
  • Delegates.notNull()可以用作任何類型的var變量進行 延遲初始化
  • 只有擁有幕后字段的屬性轉(zhuǎn)換成Java代碼時丙曙,才有對應的Java變量索抓。

參考資料:

android Kotlin系列:

Kotlin知識歸納(一) —— 基礎語法

Kotlin知識歸納(二) —— 讓函數(shù)更好調(diào)用

Kotlin知識歸納(三) —— 頂層成員與擴展

Kotlin知識歸納(四) —— 接口和類

Kotlin知識歸納(五) —— Lambda

Kotlin知識歸納(六) —— 類型系統(tǒng)

Kotlin知識歸納(七) —— 集合

Kotlin知識歸納(八) —— 序列

Kotlin知識歸納(九) —— 約定

Kotlin知識歸納(十) —— 委托

Kotlin知識歸納(十一) —— 高階函數(shù)

Kotlin知識歸納(十二) —— 泛型

Kotlin知識歸納(十三) —— 注解

Kotlin知識歸納(十四) —— 反射

image
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末纽乱,一起剝皮案震驚了整個濱河市顽爹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌券勺,老刑警劉巖缀去,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赊抖,死亡現(xiàn)場離奇詭異,居然都是意外死亡弦追,警方通過查閱死者的電腦和手機岳链,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門牵辣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了余舶?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵蹦锋,是天一觀的道長。 經(jīng)常有香客問我欧芽,道長莉掂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任千扔,我火速辦了婚禮憎妙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘曲楚。我一直安慰自己厘唾,他們只是感情好,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布龙誊。 她就那樣靜靜地躺著抚垃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪趟大。 梳的紋絲不亂的頭發(fā)上鹤树,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機與錄音逊朽,去河邊找鬼罕伯。 笑死,一個胖子當著我的面吹牛叽讳,可吹牛的內(nèi)容都是我干的追他。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼岛蚤,長吁一口氣:“原來是場噩夢啊……” “哼邑狸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起涤妒,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤单雾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后届腐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铁坎,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年犁苏,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扩所。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡围详,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情助赞,我是刑警寧澤买羞,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站雹食,受9級特大地震影響畜普,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜群叶,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一吃挑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧街立,春花似錦舶衬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至梁剔,卻和暖如春虽画,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背荣病。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工狸捕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人众雷。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓灸拍,卻偏偏與公主長得像,于是被迫代替她去往敵國和親砾省。 傳聞我的和親對象是個殘疾皇子鸡岗,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353