Kotlin - 如果不是空的话,用改进的Obj道具覆盖Obj道具

VSO*_*VSO 14 optimization dry kotlin

TL; DR:

如何减少冗余(任何有效的方法)?

if (personModification.firstName != null) {person.firstName = personModification.firstName}
if (personModification.lastName != null) {person.lastName = personModification.lastName}
if (personModification.job != null) {person.job = personModification.job}
Run Code Online (Sandbox Code Playgroud)

长版:我有一个简单的问题.我有一节课Person:

class Person (val firstName: String?, 
              val lastName: String?, 
              val job: String?)
Run Code Online (Sandbox Code Playgroud)

我有一个叫做的课PersonModification:

class PersonModification(val firstName: String?, 
                         val lastName: String?, 
                         val job: String?)
Run Code Online (Sandbox Code Playgroud)

任务是PersonPersonModification值覆盖任何属性值,如果PersonModification属性不是null.如果你关心,这背后的业务逻辑是一个API端点,它修改Person并接受一个PersonModification参数(但可以更改所有或任何属性,因此我们不希望用空值覆盖有效的旧值).对此的解决方案看起来像这样.

if (personModification.firstName != null) {person.firstName = personModification.firstName}
if (personModification.lastName != null) {person.lastName = personModification.lastName}
if (personModification.job != null) {person.job = personModification.job}
Run Code Online (Sandbox Code Playgroud)

我被告知这是多余的(我同意).解决方案伪代码如下所示:

foreach(propName in personProps){
  if (personModification["propName"] != null) {person["propName"] = personModification["propName"]}
}
Run Code Online (Sandbox Code Playgroud)

当然,这不是JavaScript,所以并不容易.我的反思解决方案如下,但是imo,拥有冗余比在这里做反射更好.删除冗余的其他选择是什么?


Refelection:

package kotlin.reflect;

class Person (val firstName: String?, 
              val lastName: String?, 
              val job: String?)

class PersonModification(val firstName: String?, 
                         val lastName: String?, 
                         val job: String?)

// Reflection - a bad solution. Impossible without it.
//https://stackoverflow.com/questions/35525122/kotlin-data-class-how-to-read-the-value-of-property-if-i-dont-know-its-name-at
inline fun <reified T : Any> Any.getThroughReflection(propertyName: String): T? {
    val getterName = "get" + propertyName.capitalize()
    return try {
        javaClass.getMethod(getterName).invoke(this) as? T
    } catch (e: NoSuchMethodException) {
        null
    }
}

fun main(args: Array<String>) {

var person: Person = Person("Bob","Dylan","Artist")
val personModification: PersonModification = PersonModification("Jane","Smith","Placeholder")
val personClassPropertyNames = listOf("firstName", "lastName", "job")

for(properyName in personClassPropertyNames) {
    println(properyName)
    val currentValue = person.getThroughReflection<String>(properyName)
    val modifiedValue = personModification.getThroughReflection<String>(properyName)
    println(currentValue)
    if(modifiedValue != null){
        //Some packages or imports are missing for "output" and "it"
        val property = outputs::class.memberProperties.find { it.name == "firstName" }
        if (property is KMutableProperty<*>) {
            property.setter.call(person, "123")
        }
    }
})
}
Run Code Online (Sandbox Code Playgroud)

您可以在此处复制并粘贴以运行它:https://try.kotlinlang.org/

Str*_*lok 7

编写一个5行帮助程序来执行此操作应该非常简单,甚至支持复制每个匹配的属性或仅选择一些属性.

虽然如果您正在编写Kotlin代码并大量使用数据类和val(不可变属性),它可能没用.看看这个:

fun <T : Any, R : Any> T.copyPropsFrom(fromObject: R, skipNulls: Boolean = true, vararg props: KProperty<*>) {
  // only consider mutable properties
  val mutableProps = this::class.memberProperties.filterIsInstance<KMutableProperty<*>>()
  // if source list is provided use that otherwise use all available properties
  val sourceProps = if (props.isEmpty()) fromObject::class.memberProperties else props.toList()
  // copy all matching
  mutableProps.forEach { targetProp ->
    sourceProps.find {
      // make sure properties have same name and compatible types 
      it.name == targetProp.name && targetProp.returnType.isSupertypeOf(it.returnType) 
    }?.let { matchingProp ->
      val copyValue = matchingProp.getter.call(fromObject);
      if (!skipNulls || (skipNulls && copyValue != null)) {
        targetProp.setter.call(this, copyValue)
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这种方法使用反射,但它使用非常轻量级的Kotlin反射.我没有计时,但它应该以与手动复制属性几乎相同的速度运行.

此外,它使用KProperty而不是字符串来定义属性的子集(如果您不希望所有属性都被复制),因此它具有完全的重构支持,因此如果您重命名该类的属性,则不必搜索字符串引用重命名.

它默认会跳过空值,或者您可以将skipNulls参数切换为false(默认值为true).

现在给出2个课程:

data class DataOne(val propA: String, val propB: String)
data class DataTwo(var propA: String = "", var propB: String = "")
Run Code Online (Sandbox Code Playgroud)

您可以执行以下操作:

  var data2 = DataTwo()
  var data1 = DataOne("a", "b")
  println("Before")
  println(data1)
  println(data2)
  // this copies all matching properties
  data2.copyPropsFrom(data1)
  println("After")
  println(data1)
  println(data2)
  data2 = DataTwo()
  data1 = DataOne("a", "b")
  println("Before")
  println(data1)
  println(data2)
  // this copies only matching properties from the provided list 
  // with complete refactoring and completion support
  data2.copyPropsFrom(data1, DataOne::propA)
  println("After")
  println(data1)
  println(data2)
Run Code Online (Sandbox Code Playgroud)

输出将是:

Before
DataOne(propA=a, propB=b)
DataTwo(propA=, propB=)
After
DataOne(propA=a, propB=b)
DataTwo(propA=a, propB=b)
Before
DataOne(propA=a, propB=b)
DataTwo(propA=, propB=)
After
DataOne(propA=a, propB=b)
DataTwo(propA=a, propB=)
Run Code Online (Sandbox Code Playgroud)