kotlin委托与扩展

类委托

类委托:一个类中定义的方法实际是调用另一个类的对象的方法来实现。
DelegatedPattern.kt

  interface PayApi{
      fun pay()
  }

  class AliPay : PayApi{
      override fun pay() {
          println("delegate AliPay")
      }
  }

  //自己实现的委托
  class ScanCodePay : PayApi{
      private val payApi: PayApi = AliPay()
      override fun pay() {
          payApi.pay()
      }
  }

  fun main(){
      ScanCodePay().pay()
  }

问题:假设接口PayApi 有许多的方法,两个实现类都需重写这些方法,那么ScanCodePay的每一个方法都委托给Alipay,代码冗余 (java实现可考虑使用动态代理)
在Kotlin中的类委托:更加优雅,简洁,通过关键字 by 实现委托。

  //待实现的接口 + by + 委托对象
  class ScanCodePay : PayApi by AliPay()

类委托实现原理

通过将kotlin反编译为java代码(Tools->Kotlin->show Kotlin BytesCode ->Decompile)

public final class ScanCodePay implements PayApi {
  private final PayApi payApi = (PayApi)(new AliPay());

  public void pay() {
          this.payApi.pay();
      }
}

编译器会自动在被委托类中添加了一个委托类对象,交由委托对象实现,类似委托模式。

  • 委托模式:有两个对象参与处理同一请求,则接受请求的对象将请求委托给另一个对象来处理。
    简单来说,就是操作的对象不用自己去执行操作,而是将任务交给另一个对象操作。kotlin有类委托,属性委托。

属性委托

有一些常见的属性类型,虽然我们可以在每次需要的时候手动实现它们, 但是如果能够将一次性实现并放入一个库会更方便。例如:

  • 延迟属性(lazy properties): 其值只在首次访问时计算;
  • 可观察属性(observable properties): 监听器会收到有关此属性变更的通知;
  • 映射属性(map properties): 把多个属性储存在一个map中,而不是每个存在单独的字段中。

为了涵盖这些情况,kotlin支持属性委托。
属性委托:一个类的某个属性值不在类中直接定义,而是将其委托给一个代理类,从而实现对该类的属性进行统一管理。

a. 定义一个类作为属性委托类,并提供两个方法:getValue()、setValue(),他们的方法签名必须按照如下格式:

// thisRef:进行委托的类的对象   property:属性对象
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {} 

b. 在被委托的属性 后添加:by 委托对象

val/var <属性名>: <类型> by <委托代理类>

PropertyDelegated.kt

class Bean(){
  var name : String by Delegater()
}

class Delegater {
  var str : String = ""
  operator fun getValue(ref: Any?, p: KProperty<*>) :String {
      println("get ${p.name} --> $str")
      return str
  }
  // ref  进行委托的类的对象 p 属性对象 value 属性的值
  operator fun setValue(ref: Any?, p: KProperty<*>, value: String) {
      str = value
      println("set ${p.name} --> $str")
  }
}

属性委托实现原理

通过查看java代码的方式 发现属性委托与类委托的原理很接近,持有委托类的对象,并且持有属性集合,调用它的getValue()和setValue()方法,对属性进行读写。
对于val类型的属性,只需提供一个getValue()方法即可。

fun main() {
  var bean = Bean()
  bean.name = "Tim"  // setValue set name --> Tim
  bean.name          // getValue get name --> Tim
}

属性委托的意义体现在它的各种类型的属性上。

延迟属性

by + lazy + lambda表达式

LazyPropertyDelegated.kt

val lazyValue: String by lazy {
  println("property lazy")
  "lazyValue"
}

使用场景:延迟val属性的初始化时机(第一次访问的时候才会去初始化)
lazyValue 对象在访问之前,不会初始化,状态为:Lazy value not initialized yet
第一次访问被委托的属性时,lambada表达式会执行一次,并记录它返回值。之后再访问这个被委托属性时,直接使用记录的返回值。
使用 lateinit 关键字修饰变量,处理无法在构造函数中初始化的变量,避免后续使用变量时的判空操作(如:loginBtn?.text=...)

class MyActivity : Activity() {
  private lateinit var loginBtn: Button
  override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      loginBtn = findViewById(R.id.login_button)
  }
}

println(lazyValue) 执行三次,结果如下:

property lazy
lazyValue
lazyValue
lazyValue

这熟悉的使用场景很像单例。单例为了解决多线程同步的问题: 有很多方法:静态内部类 、饿汉式等
lazy是如何做到的?lazy的实现?

LazyJVM.kt 源码

/**
* Creates a new instance of the [Lazy] that uses the specified initialization function [initializer]
* and the default thread-safety mode [LazyThreadSafetyMode.SYNCHRONIZED].
*
* If the initialization of a value throws an exception, it will attempt to reinitialize the value at next access.
*
* Note that the returned instance uses itself to synchronize on. Do not synchronize from external code on
* the returned instance as it may cause accidental deadlock. Also this behavior can be changed in the future.
*/
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
//查看源码 得知 SynchronizedLazyImpl方法内部时通过同步锁实现的
···
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
  when (mode) {
      LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
      LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
      LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
  }
··· 

LazyThreadSafetyMode.SYNCHRONIZED 这种模式下,lambda表达式中,通过添加同步锁的方式,确保只有一个线程能够来执行初始化,确保只被初始化一次。

  • Note: Do not synchronize from external code

lazy是只读属性的委托对象,查源码只包含getValue方法。

可观察属性

观察者代码 ObserverPattern.kt

class StockUpdateOb : Observable() {
  val observers = mutableSetOf<Observer>()

  fun setStockChanged(price: Double){
      observers.forEach{
          it.update(this,price)
      }
  }
}

class StockDisplayOb : Observer{
  override fun update(observable: Observable, price: Any) {
      if (observable is StockUpdateOb) {
          ...
          println("the latest stock price is ${price}.")
      }
      else {
          ...
      }
  }
}

Kotlin实现观察者模式使用了java标准库中的类和方法。但实现java.util.Observer接口只能重写update方法,如果有多种变更逻辑都要体现在update方法里,就要通过逻辑区分,使得代码臃肿。
示例:ObserverPatternDelegated.kt
额外引入可被观察的委托属性,一定程度实现了解耦。

class StockUpdate  {
  private val initialPrice: Double = 10.0
  // 观察者集合
  val listeners = mutableSetOf<StockUpdateListener>()
  var price : Double by Delegates.observable(initialPrice) { _, oldValue, newValue ->
      listeners.forEach {
          if (newValue > oldValue) {
              it.onRise(price)
          }
          else {
              it.onFall(price)
          }
      }
  }
}

interface StockUpdateListener{
  fun onRise(price: Double)
  fun onFall(price: Double)
}

class StockDisplay : StockUpdateListener{
  override fun onRise(price: Double) {
      ...
      println("the latest stock price has risen to ${price}.")
  }

  override fun onFall(price: Double) {
      ...
      println("the latest stock price has fall to ${price}.")
  }
}

ObserverPatternDelegated.txt

Delegates.observable 源码

/**
* Returns a property delegate for a read/write property that calls a specified callback function when changed.
* @param initialValue the initial value of the property.
* @param onChange the callback which is called after the change of the property is made. The value of the property
*  has already been changed when this callback is invoked.
*
*  @sample samples.properties.Delegates.observableDelegate
*/
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)
  }

使用Delegates.observable()的灵活性,observable接收两个参数:一个初始值,赋给被委托属性;一个lambda表达式,
lambda有三个回调参数,描述属性的KProperty、旧值以及新值。observable方法的返回值类型为ReadWriteProperty

ReadWriteProperty源码

/**
 * Base interface that can be used for implementing property delegates of read-write properties.
 */
  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)
}

ObservableProperty源码

public abstract class ObservableProperty<T>(initialValue: T) : ReadWriteProperty<Any?, T> {
  private var value = initialValue
    ...
    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)
    }
}

实现原理:ReadWriteProperty的实现类为ObservableProperty,委托对象就是这个ObservableProperty抽象类。当 ObservableProperty<T>(initialValue)一旦被委托属性的值发生变化(即调用set方法)时,会回调ObservableProperty#setValue方法,在setValue中,调用了afterChange(),而afterChange的实现即:onChange 参数即在使用该委托的时候传入的lambda表达式。
所以每次修改该对象的值的时候,都会调用传入的函数,实现了对对象的值改变的观察.

与之对应的ReadOnlyProperty 只有一个getValue 方法,服务于val属性。

同observable委托有类似功能的还有一个:vetoable

Delegates.vetoable源码

 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)
}

lambda会的返回值,与observable和vetoable的回调时机不同有关:observable的回调时机是在属性值修改之后,vetoable的回调时机在属性值被修改之前。(对应setValue方法中的beforeChangeafterChangebeforeChange方法如果返回值为true,属性值就会被修改成新值;如果返回值为false,此次修改就会直接被丢弃。

map委托

特点: 对于属性的访问,直接委托给一个map对象。
要求:map的key要同属性名保持一致。
示例MapDelegated.kt

class Account(map: Map<String,Any?>){
    val username:String by map
    val password:String by map
}

fun mapDelegated(){
    val map:Map<String,Any?> = mapOf(
            "username" to "Lee",
            "password" to "******"
    )
    val account = Account(map)
    //account.username 即 map["username"]
    //account.password 即 map["password"]
    map["username"] = ""  //Compile Error:hint No set method
}
fun mutableMapDelegated() {
    val mutableMap: MutableMap<String, Any?> = mutableMapOf(
            "name" to "Alice",
            "age" to 20,
            "address" to "beijing"
    )

    val student = Student(mutableMap)
    student.address = "shanghai"
    println("mutableMap address:  " +  mutableMap["address"])  // shanghai
    mutableMap["address"] = "beijing"
    println("mutableMap address:  " + student.address)     // beijing
}

对比可以得知:
val的map委托的对象是Map<String, Any?>,var的map委托的对象MutableMap<String, Any?>
对于var属性,对于MutableMap中的value的修改,会同步到属性值;反之亦然。
使用场景:将map中key-value映射到对象的属性中,这通常在解析json数据时用到

委托代替多继承

MultiInhert.kt
java中无多继承,是因为多继承的“菱形”问题,会产生歧义。
而kotlin可以通过super关键字,解决多继承的“菱形”问题。

interface IPay {
    fun pay() = "pay for Cash"
    fun change() = "change change change"
}

interface IChange {
    fun change() = "give change"
}

open class ScanCodeForPay : IPay {
    override fun pay() =  "pay for scanCode"
}

open class  NoChange: IChange {
    override fun change() = "no change"
}
class Transaction(pay:ScanCodeForPay,change: NoChange) : IPay by pay, IChange by change{
    override fun change(): String {
        return super<IPay>.change() 
    }
}

若同时实现多个接口,且接口间有相同的方法名的默认实现,需要主动指定使用的接口方法,或重写方法。
MultiInhert.txt

委托的优点、使用场景

提供了精简的方式来实现类似java静态代理的方式,观察者模式,以及懒加载的方式,代替多继承。

kotlin扩展

Kotlin 在不修改类源代码的情况下,“动态”地为类添加属性(扩展属性)和方法(扩展方法)且不需要继承或使用 Decorator 模式。
扩展是一种静态行为,对被扩展的类代码本身不会造成任何影响。

扩展函数

示例1 给String扩展一个函数

 // 目标类型.扩展函数名
fun String.firstChar():String{
...
}

示例2:ExtendMethod.kt 给List扩展一个带参数的函数

fun <T> List<T>.filter(predicate: (T) -> Boolean): MutableList<T> {
    val result = ArrayList<T>()
    forEach {
        if (predicate(it)) {
            result.add(it)
        }
    }
    return result
}

fun filterExtend(){
    val list = mutableListOf(0,1,2,3,4,5,6,7)
    val result = list.filter { it%2 == 0 }
    println(result.toString()) //[0, 2, 4, 6]
}
extendMethod.png

如何实现的?
通过反编译为java代码可知:
扩展函数生成了一个静态的方法,当Kotlin调用扩展函数时, 编译器将会调用生成的函数并且把相应的对象传入。
由此也了解到扩展函数不会带来额外的性能消耗。

扩展函数的作用域

通常将扩展函数直接定义在包内,作用域、调用方式与java全局静态方法类似。
如果定义到类的内部时,只是相当于在扩展类内部的方法,通过反编译为Java代码可知,扩展方法不再是静态方法。

静态扩展

在kotlin中声明一个静态扩展,则需将其定义在Companion Object
Student.kt

class Student {
    var name: String = ""
    var age: Int = 0
    var no: String = ""

    companion object { //若无伴生对象,则需定义
    }
}

fun Student.Companion.changeName(name:String):String {
    ...
}

方便直接静态调用,无需创建实例

扩展属性

kotlin允许动态为类扩展属性,扩展属性是通过添加get、set方法实现.
示例 ExtendFiled.txt

extendMethod_parmas.png

看java代码得知:
扩展没有实际将成员插入类中(没有真正的被定义出来),只是为该类添加get、set方法。因此扩展属性并没有幕后字段(filed)。


demo_extendProperty.png

幕后字段:在Kotlin中, 如果属性在至少一个访问器中(getter读访问器,setter写访问器)使用默认实现,那么Kotlin会自动提供幕后字段,有幕后字段的属性转换成Java代码一定有一个对应的Java变量

扩展属性的限制:

  1. 扩展属性不能有初始值;
  2. val必须提供get方法,var必须提供get和set方法。

成员方法优先

ExtendAndMember.kt
同名的类成员方法的优先级总是高于扩展函数。

class Member{
    fun getName(){
        println("get Member's Name")
    }
}

fun Member.getName(){
    println("get Member's extend Name")
}

fun readName(){
    Member().getName() // get Member's Name
}

可通过反编译java代码查看,在编译阶段已经确定,且Android studio 编译器会高亮提示。
此设计解决的问题:多人开发,各自扩展同名方法,造成不一致的结果,对于第三方类库更甚。

类的实例与接收者实例

this的使用在Kotlin中比java灵活。
1.在扩展函数里调用this,指代的是接收者类型(即对谁扩展)的实例。
2.在扩展函数内部,想要获取到类的实例
需要 this@ClassName.extendMethod() 此方式称为:以类成员的方式定义扩展
在某个类里面为其他类定义扩展方法、属性,该扩展的方法,只能在该类中通过被扩展的类的对象调用扩展方法。
通过查看java代码,发现不再为static public 而为 public,就如一个类的成员方法

demo_extendMethod.png

扩展函数是静态解析的

在编译时执行,根据调用对象,方法名找到拓展函数。
eg:根据多态的方式,引用类型为父类,生成子类对象。但是编译阶段,静态调用,所以关注的引用类型。

ExtendStaticParse.kt
open class Base
class Extended: Base()
fun Base.cook() = "I'm Base.cook! "
fun Extended.cook() = "I'm Extended.cook! "

fun main() {
    val instance: Base = Extended()
    val instance2 = Extended()
    println(instance.cook())   //"I'm Base.cook! "
    println(instance2.cook())  //"I'm Extended.cook! "
}

对应的java代码

public static final String cook(@NotNull Base $this$cook)  //传参类型取决于引用类型

标准库中的扩展函数

run

/**
 * Calls the specified function [block] with `this` value as its receiver and returns its result.
 */                                  
public inline fun <T, R> T.run(block: T.() -> R): R {
    ...
    return block()
}
//使用示例
 item.run {
    // (this value as its receiver)
    holder.tv?.setText(name) //item.name / this.name
 }

run是任何类型T的通用扩展函数,执行返回类型为R的 扩展函数block(),最终返回block()表达式的结果。

/**
 * Calls the specified function [block] and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
    ...
    return block()
}
//使用示例  非扩展函数
run {
    if(!isLogin) 
      loginDialog
    else 
      newAccountDialog  
}.show()

仅返回表达式的结果。

with

/**
 * Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    ...
    return receiver.block()
}

第一个参数:接收者对象
第二个参数:接收者对象的扩展方法,且返回值为R
返回值:第二个参数的结果

    with(Student()) {
        name = "Kitty"  //this.name   student.name
        age = 11
        ageToString()
        no = "010101010"
    }

通过Java代码可以看出,就是对传入参数本身进行处理
使用场景: 可调用一个对象的多个方法

Apply、Also

**
 *  with `this` value as its receiver and returns `this` value.
 */
public inline fun <T> T.apply(block: T.() -> Unit): T {
    ...
    block()
    return this
}

/**
 *  with `this` value as its argument and returns `this` value.
 */
public inline fun <T> T.also(block: (T) -> Unit): T {
    ...
    block(this)
    return this
}

二者返回值是函数的接收者
使用 apply 为对象的属性赋值
场景:构造函数,为构造对象初始化属性,然后返回构造对象。eg:自定义View,设置属性,再返回这个View

示例 ExtendApplyAndAlsoClass.kt

class ExtendApplyAndAlsoClass {
    val student: Student? = getStu()
    var age = 11
    fun dealStuWithAlso() {
        Student.changeName("")
        val result = student?.also { stu ->
            println(this.age)  //11 this == ExtendApplyAndAlsoClass.instance
        }
    }

    fun dealStuWithApply(){
        val result = student?.apply {
            println(this.age)  // 10 this == student
        }
    }

    fun getStu():Student{
        var stu: Student = Student()
        stu.age = 10
        return stu
    }
}

区别:this的指代不同
apply内部为扩展方法,this指代为接收者。
在aslo内部,this指代调用类的实例。

let

let的使用类似also,但返回值不同
返回值为函数块的最后一行或指定return表达式。

takeIf

/**
 * Returns `this` value if it satisfies the given [predicate] or `null`, if it doesn't.
 */
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
    ...
    return if (predicate(this)) this else null
}

返回值,为boolean或者null 结合let一起使用

fun getStu():Student{
    var stu = Student()
    stu.age = 10
    return stu
}
val result = getStu().takeIf {
    it.age >=18
}?.let {  //?容易遗漏
    println("go to internet cafe")
}

fun main(){
    println(result)
}

以上的方法,区别:传参,返回值类型,以及this指代。

扩展在android中的使用

通过import kotlinx.android.synthetic.main的方式
取代findViewById
如何实现的?

查看Java代码会发现第一次使用控件时,在缓存集合中进行查找,有则使用,无则通过findViewById进行查找,并将其添加至缓存集合中。而且提供了clearFindViewByIdCache()方法用于清除缓存
Fragment的onDestroyView()方法中默认调用了clearFindViewByIdCache()清除缓存,而Activity没有。
由此可见,没有抛弃使用findViewById(),只是Kotlin的扩展插件利用缓存的方式,使得开发更方便、快捷。

扩展的优点

扩展极大的增加了程序的灵活性,简洁性。

扩展使用场景

在第三方库不满足需求时,为遵循开闭原则,不修改源码的方式,对其进行扩展,java代码只能使用继承,而kotlin直接可以动态的扩展,且能更方便组织一些工具方法。
Google推出的Android扩展库 Android KXT

参考文档

书籍《Kotlin核心编程》
Ktolin源码

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,511评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,495评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,595评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,558评论 0 190
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,715评论 3 270
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,672评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,112评论 2 291
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,837评论 0 181
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,417评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,928评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,316评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,773评论 2 234
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,253评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,827评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,440评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,523评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,583评论 2 249