Как уменьшить код с помощью обобщений и, возможно, отражения - PullRequest
0 голосов
/ 13 февраля 2019

У меня есть объекты DTO, тогда мне нужно динамически отсортировать коллекцию объектов, вроде базы данных ORDER BY, но у меня нет базы данных, которая фактически выполняла бы запросы на самом деле (я знаю, но эточто у меня сегодня ...).

Мне кажется, что StatusComparator и TypeComparator должны быть в состоянии переписать как полностью универсальные и, вероятно, также использовать отражение, так что мне не нужно писать один для каждого объекта в моем API (яесть еще три для этой услуги, и это становится действительно повторным).

Как только я пойму правильный способ написания этого, я планирую извлечь Компараторы в их собственную библиотеку, чтобы я мог поделиться шаблоном с другими подразделениями в моей компании, чтобы облегчить написание их кода.

Этот код написан на Kotlin, поэтому очень хотелось бы максимально сосредоточиться на этой реализации.

DTO:

@Table("type")
data class TypeObject(

    @get:NotNull
    @PrimaryKey
    @JsonProperty("id") val id: String,

    @get:NotNull
    @Column("type")
    @JsonProperty("type") val type: String,

    @Column("is_deleted")
    @JsonProperty("isDeleted") val isDeleted: Boolean? = null
)

@Table("status")
data class StatusObject(
    @get:NotNull
    @PrimaryKey
    @JsonProperty("id") val id: String,

    @get:NotNull
    @JsonProperty("status") val status: String,

    @Column("is_deleted")
    @JsonProperty("isDeleted") val isDeleted: Boolean? = null
)

Сравнение:

@Component
class StatusComparator<T : StatusObject> {
    fun buildComparator(
        field: String,
        asc: Boolean
    ): Comparator<T> {
        return if (asc) {
            compareBy {
                getField(field, it)
            }
        } else {
            compareByDescending {
                getField(field, it)
            }
        }
    }

    private fun getField(
        field: String,
        it: StatusObject
    ): Comparable<*>? {
        return when (field.toLowerCase()) {
            "id" -> it.id
            "status" -> it.status
            else -> it.isDeleted
        }
    }
}

@Component
class TypeComparator<T : TypeObject> {
    fun buildComparator(
        field: String,
        asc: Boolean
    ): Comparator<T> {
        return if (asc) {
            compareBy {
                getField(field, it)
            }
        } else {
            compareByDescending {
                getField(field, it)
            }
        }
    }

    private fun getField(
        field: String,
        it: TypeObject
    ): Comparable<*>? {
        return when (field.toLowerCase()) {
            "id" -> it.id
            "type" -> it.type
            else -> it.isDeleted
        }
    }
}

Пример использования в моей службе типов:

@Service
class TypeApiServiceImpl(
    private val repo: TypeRepository,
    private val sortListBuilder: SortListBuilder,
    private val customComparator: TypeComparator<TypeObject>
) : TypeApiService {

    override fun get(
        sort: String,
        filterId: UUID,
        filterType: String,
        filterIsDeleted: Boolean
    ): Mono<DocumentTierModels> {
        return if (filterId != UUID.fromString("00000000-0000-0000-0000-000000000000")) {
            this.getTypeById(filterId)
        } else {
            val objects = this.getTypeByFilter(filterType, filterIsDeleted)

            if (sort != "null") {
                this.getSortedTypes(sort, objects)
            } else {
                TypesModels(objects, MetaModel(null, listOf())).toMono()
            }
        }
    }

    private fun sortObject(
        objects: List<TypeObject>,
        sortItems: List<String>
    ): List<TypeObject> {
        when (sortItems.count()) {
            1 -> {
                val fieldAndDirection1 = sortItems[0].split(',')

                return objects
                    .sortedWith(customComparator.buildComparator(fieldAndDirection1[0], fieldAndDirection1[1] == "asc"))
            }
            2 -> {
                val fieldAndDirection1 = sortItems[0].split(',')
                val fieldAndDirection2 = sortItems[1].split(',')

                return objects
                    .sortedWith(
                        customComparator.buildComparator(fieldAndDirection1[0], fieldAndDirection1[1] == "asc")
                            .then(customComparator.buildComparator(fieldAndDirection2[0], fieldAndDirection2[1] == "asc"))
                    )
            }
            3 -> {
                val fieldAndDirection1 = sortItems[0].split(',')
                val fieldAndDirection2 = sortItems[1].split(',')
                val fieldAndDirection3 = sortItems[2].split(',')

                return objects
                    .sortedWith(
                        customComparator.buildComparator(fieldAndDirection1[0], fieldAndDirection1[1] == "asc")
                            .then(customComparator.buildComparator(fieldAndDirection2[0], fieldAndDirection2[1] == "asc"))
                            .then(customComparator.buildComparator(fieldAndDirection3[0], fieldAndDirection3[1] == "asc"))
                    )
            }
            else -> {
                return objects
            }
        }
    }
}

Пример использования в моей службе состояний:

@Service
class StatusesApiServiceImpl(
    private val repo: StatusRepository,
    private val sortListBuilder: SortListBuilder,
    private val customComparator: StatusComparator<StatusObject>
) : StatusesApiService {

    override fun get(
        sort: String,
        filterId: UUID,
        filterStatus: String,
        filterIsDeleted: Boolean
    ): Mono<StatusModels> {
        return if (filterId != UUID.fromString("00000000-0000-0000-0000-000000000000")) {
            this.getStatusById(filterId)
        } else {
            val objects = this.getStatusByFilter(filterStatus, filterIsDeleted)

            if (sort != "null") {
                this.getSortedStatuses(sort, objects)
            } else {
                StatusModels(objects, MetaModel(null, listOf())).toMono()
            }
        }
    }

    private fun getSortedStatuses(
        sort: String,
        objects: List<StatusObject>
    ): Mono<StatusModels> {
        var objects1 = objects
        var sortList: MutableList<String> = mutableListOf()
        val sortItems = sortListBuilder.getSortItems(sort)

        if (sortItems != null) {
            sortList = sortListBuilder.buildSortList(sortItems)
            objects1 = this.sortObject(objects1, sortItems)
        }

        val meta = MetaModel(null, sortList)

        return StatusModels(objects1, meta).toMono()
    }

    private fun sortObject(
        objects: List<StatusObject>,
        sortItems: List<String>
    ): List<StatusObject> {
        when (sortItems.count()) {
            1 -> {
                val fieldAndDirection1 = sortItems[0].split(',')

                return objects
                    .sortedWith(customComparator.buildComparator(fieldAndDirection1[0], fieldAndDirection1[1] == "asc"))
            }
            2 -> {

                val fieldAndDirection1 = sortItems[0].split(',')
                val fieldAndDirection2 = sortItems[1].split(',')

                return objects
                    .sortedWith(customComparator.buildComparator(fieldAndDirection1[0], fieldAndDirection1[1] == "asc")
                        .then(customComparator.buildComparator(fieldAndDirection2[0], fieldAndDirection2[1] == "asc")))
            }
            3 -> {
                val fieldAndDirection1 = sortItems[0].split(',')
                val fieldAndDirection2 = sortItems[1].split(',')
                val fieldAndDirection3 = sortItems[2].split(',')

                return objects
                    .sortedWith(customComparator.buildComparator(fieldAndDirection1[0], fieldAndDirection1[1] == "asc")
                        .then(customComparator.buildComparator(fieldAndDirection2[0], fieldAndDirection2[1] == "asc"))
                        .then(customComparator.buildComparator(fieldAndDirection3[0], fieldAndDirection3[1] == "asc")))
            }
            else -> {
                return objects
            }
        }
    }
}

Теперь я также вижу в своем вопросе, что я также могу применить этошаблон для моих реальных услуг, но давайте сделаем это один шаг за раз.

1 Ответ

0 голосов
/ 14 февраля 2019

Отражение

@Suppress("UNCHECKED_CAST")
fun <T : Any> KClass<T>.compareByProperty(propName: String, asc: Boolean = true): Comparator<T> {
    val property = declaredMemberProperties.first { it.name == propName }
    val getter = property::get as (T) -> Comparable<*>

    if (asc) {
        return compareBy(getter)
    }
    return compareByDescending(getter)
}

Первая функция расширения compareByProperty использует отражение для поиска свойства по имени, а затем извлекает получатель свойства как селектор Conparable

Затемв зависимости от параметра asc getter преобразуется в Comparator универсального типа со стандартной функцией compareBy и compareByDescending:

Помощник для объединения

inline fun <reified T : Any> Comparator<T>.thenByProperty(propName: String, asc: Boolean = true) =
    then(T::class.compareByProperty(propName, asc))

Вторая функция расширения позволяет комбинировать Comparator s:

Использование

fun main() {
    val typeObjects = listOf(...)
    val comparator = TypeObject::class.compareByProperty("id")
        .thenByProperty("type", asc = false)

    val sortedTypes = typeObjects.sortedWith(comparator)
}

Использование выглядит в этом примере как основная функция.Получите доступ к объекту KClass через ::.Затем вызовите первую функцию расширения и используйте вторую функцию расширения для объединения Comparable s:

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...