У меня есть объекты 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
}
}
}
}
Теперь я также вижу в своем вопросе, что я также могу применить этошаблон для моих реальных услуг, но давайте сделаем это один шаг за раз.