Tipos de unión / interfaces de extensión

Tengo varias data class con campos, que se usan en formularios y los necesito para que un método devuelva true si se ha llenado alguno de los campos.

No quiero volver a escribir esto para todas las classs, así que lo estoy haciendo así en este momento:

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

Esto obviamente no es muy bonito ni seguro para el tipo.

¿Cuál es una forma más idiomática de hacer esto, sin abstraer cada propiedad en algún tipo de Field type?

Aclaración: lo que estoy buscando es una forma de ser exhaustivo when , por ejemplo, proporcionando todos los types permitidos ( String , Int , List , Set ) y una function para cada uno para decir si están vacíos. Al igual que una "interfaz de extensión" con un método isEmptyFormField .

Si todo lo que está haciendo es search isEmpty / isBlank / isZero / etc. entonces probablemente no necesite una function checkEmpty genérica, etc.

 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 } 

Sin embargo, si realmente haces algo un poco más complejo, en function de tu aclaración agregada, creo que "resumir cada propiedad en un tipo de Field tipo" es exactamente lo que quieres, simplemente no hagas que las instancias Field formen parte de cada data class sino que crea una list de ellos cuando sea necesario:

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

Esto le proporciona security de tipo sin cambiar sus componentes de data class , etc. y le permite "get exhaustivo when ".

Es un poco raro, pero debería funcionar. Cada data class crea un set de methods por cada parámetro de constructor. Se llaman componentN() (donde N es el número que comienza desde 1 indica el parámetro constructor).

Puede poner dichos methods en su interfaz y hacer que data class implemente implícitamente. Vea el siguiente ejemplo:

 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 } 

También puedes agregar el fun component3(): Any? = null fun component3(): Any? = null etc … para manejar casos con más de 2 campos en data class (por ejemplo, patrón NullObject o manejo null s directamente en su método checkEmpty() .

Como dije, es un poco hacky pero quizás funcione para ti.

Puede usar null para indicar "no especificado":

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

La idea aquí es la misma que la de una Optional<T> en Java pero sin el object extra, etc.

Ahora debe preocuparse por la security nula, pero si sus campos están destinados a tener un concepto de ausencia / vacío, esto parece apropiado ( UsingAndAvoidingNullExplained · google / guava Wiki ).