【问题标题】:Union types / extension interfaces联合类型/扩展接口
【发布时间】:2016-08-18 17:13:12
【问题描述】:

我有几个带有字段的data class,它们在表单中使用,如果任何字段已填写,则需要它们有一个方法返回true

我不想为所有的类都重写这个,所以我现在这样做:

data class Order(var consumer: String, var pdfs: List<URI>): Form  {

    override val isEmpty(): Boolean
         get() = checkEmpty(consumer, pdfs)
}

data class SomethingElse(var str: String, var set: Set<String>): Form  {

    override val isEmpty(): Boolean
         get() = checkEmpty(str, set)
}


interface Form {
    val isEmpty: Boolean

    fun <T> checkEmpty(vararg fields: T): Boolean {
        for (f in fields) {
            when (f) {
                is Collection<*> -> if (!f.isEmpty()) return false
                is CharSequence -> if (!f.isBlank()) return false
            }
        }
        return true;
    }
}

这显然不是很漂亮也不是类型安全的。

在不将每个属性抽象为某种Field-type 的情况下,还有什么更惯用的方法?

澄清:我正在寻找的是一种详尽的when,例如通过提供所有允许的类型(StringIntList、@ 987654329@) 和一个函数来判断它们是否为空。就像一个带有 isEmptyFormField 方法的“扩展接口”。

【问题讨论】:

    标签: oop interface kotlin


    【解决方案1】:

    这有点老套,但应该可以。 每个data class 为每个构造函数参数创建一组方法。它们被称为componentN()(其中N 是从1 开始的数字,表示构造函数参数)。

    您可以将这些方法放在您的界面中,并让data class 隐式实现它们。请参见下面的示例:

    data class Order(var consumer: String, var pdfs: List) : Form
    
    data class SomethingElse(var str: String, var set: Set) : Form
    
    interface Form {
        val isEmpty: Boolean
            get() = checkEmpty(component1(), component2())
    
        fun  checkEmpty(vararg fields: T): Boolean {
            for (f in fields) {
                when (f) {
                    is Collection -> if (!f.isEmpty()) return false
                    is CharSequence -> if (!f.isBlank()) return false
                }
            }
            return true;
        }
    
        fun component1(): Any? = null
        fun component2(): Any? = null
    }
    

    您还可以添加fun component3(): Any? = null 等...以处理data class 中超过2 个字段的案例(例如NullObject 模式或直接在checkEmpty() 方法中处理nulls。

    正如我所说,这有点老套,但也许对你有用。

    【讨论】:

    • 好主意。您还可以使用反射来列出组件函数,而不是创建可为空的 componentN 方法。
    • 这是个好主意,但我一直在寻找的是一种详尽的方法when。例如。提供如何判断任意类型是否为“空”的功能。 (isBlank() 用于字符串,isEmpty() 用于集合等等)...
    【解决方案2】:

    如果您所做的只是检查isEmpty/isBlank/isZero/等。那么您可能不需要通用的checkEmpty 函数等:

    data class Order(var consumer: String, var pdfs: List<URI>) : Form {
        override val isEmpty: Boolean
            get() = consumer.isEmpty() && pdfs.isEmpty()
    }
    
    data class SomethingElse(var str: String, var set: Set<String>) : Form {
        override val isEmpty: Boolean
            get() = str.isEmpty() && set.isEmpty()
    }
    
    interface Form {
        val isEmpty: Boolean
    }
    

    但是,如果您实际上要做一些更复杂的事情,那么根据您添加的说明,我相信“将每个属性抽象为某种 Field-type”正是您想要的,只是不要使 @ 987654327@ 实例是每个 data class 的一部分,而是在需要时创建它们的列表:

    data class Order(var consumer: String, var pdfs: List<URI>) : Form {
        override val fields: List<Field<*>>
            get() = listOf(consumer.toField(), pdfs.toField())
    }
    
    data class SomethingElse(var str: String, var set: Set<String>) : Form {
        override val fields: List<Field<*>>
            get() = listOf(str.toField(), set.toField())
    }
    
    interface Form {
        val isEmpty: Boolean
            get() = fields.all(Field<*>::isEmpty)
    
        val fields: List<Field<*>>
    }
    
    fun String.toField(): Field<String> = StringField(this)
    fun <C : Collection<*>> C.toField(): Field<C> = CollectionField(this)
    
    interface Field<out T> {
        val value: T
        val isEmpty: Boolean
    }
    
    data class StringField(override val value: String) : Field<String> {
        override val isEmpty: Boolean
            get() = value.isEmpty()
    }
    
    data class CollectionField<out C : Collection<*>>(override val value: C) : Field<C> {
        override val isEmpty: Boolean
            get() = value.isEmpty()
    }
    

    这为您提供类型安全,而无需更改您的 data class 组件等,并允许您“获取详尽的 when”。

    【讨论】:

    • 仍然不完全满意,因为它非常冗长,但我会接受这个答案。谢谢。
    【解决方案3】:

    您可以使用null 表示“未指定”:

    data class Order(var consumer: String?, var pdfs: List<URI>?) : Form {
        override val isEmpty: Boolean
            get() = checkEmpty(consumer, pdfs)
    }
    
    data class SomethingElse(var str: String?, var set: Set<String>?) : Form {
        override val isEmpty: Boolean
            get() = checkEmpty(str, set)
    }
    
    interface Form {
        val isEmpty: Boolean
        fun <T> checkEmpty(vararg fields: T): Boolean = fields.all { field -> field == null }
    }
    

    这里的想法与 Java 中的 Optional&lt;T&gt; 相同,但没有额外的对象等。

    您现在必须担心null safety,但如果您的字段意味着有缺席/空的概念,那么这似乎是合适的 (UsingAndAvoidingNullExplained · google/guava Wiki)。

    【讨论】:

    • 这样我在使用这些类的代码中失去了很多空安全性。我必须记住在 Lists 为空时将它们设置为 null 等等……
    猜你喜欢
    • 1970-01-01
    • 2019-11-02
    • 1970-01-01
    • 2021-08-07
    • 2023-02-21
    • 2021-08-07
    • 2015-04-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多