[Kotlin] λ κ°μ²΄μ μλ‘ λ€λ₯Έ νλμ κ° μ°ΎκΈ°
π οΈ κ°λ° νκ²½
- kotlin : 1.8.22
π¬ μν© μ€λͺ
νμ¬μμ κ°λ°μ νλ μ€, λμΌν ν΄λμ€μ λν λ μΈμ€ν΄μ€μ κ°μ λΉκ΅ν΄ λ€λ₯Έ λΆλΆμ μ°Ύμ νλ‘ νΈμμ 보μ¬μ£Όλ λ‘μ§μ΄ νμνλ€.
enum class PostCategory(val title: String) {
C1("μμ κ²μν"), C2("μ 보 κ²μν")
override fun toString(): String {
return title
}
}
class Post() {
var title: String = ""
var content: String = ""
var isDeleted: Boolean = false
var category: PostCategory = PostCategory.C1
var writer: Member
}
μλ₯Ό λ€μ΄, μμ κ°μ΄ μμ λ, μ λͺ©μ΄ λ³κ²½λμλ€λ©΄ (μ λͺ© : κΈ°μ‘΄ λ΄μ© -> λ°λ λ΄μ©) μ΄λ° μμΌλ‘ 보μ¬μ€μΌνλ κ²μ΄λ€. μ°μ νμ¬μμλ κΈνκ² κ°λ°μ νλ€λ³΄λ λ€μκ³Ό κ°μ΄ ꡬννλ€.
/**
* λ³νλ λΆλΆμ μ°Ύμ 리μ€νΈλ‘ λ°ννλ ν¨μ
*/
fun findChanges(
origin: Post,
target: Post
): List<Triple<String, String, String>> {
val list = mutableListOf<Triple<String, String, String>>()
if (origin.title != target.title) {
list += Triple("μ λͺ©", origin.title, target.title)
}
if (origin.content != target.content) {
list += Triple("λ΄μ©", origin.content, target.content)
}
if (origin.isDeleted != target.isDeleted) {
val t1 = if(origin.isDeleted) "μμ λ¨" else "μμ λμ§ μμ"
val t2 = if(target.isDeleted) "μμ λ¨" else "μμ λμ§ μμ"
list += Triple("μμ μ¬λΆ", t1, t2)
}
if (origin.category != target.category) {
list += Triple("μΉ΄ν
κ³ λ¦¬", origin.category.title, target.category.title)
}
return list
}
μμ μμλ νλκ° 4κ°μΈ κ°λ¨ν Post
λ₯Ό μμλ‘ μμ±νμ§λ§, μ€μ μ½λμμ λΉκ΅ν νλλ 8κ°μ§ μ λ λλ€. μ΄λ κ² μμ±μ νλ€λ³΄λ λͺ¨λ νλλ₯Ό νλνλ μ§μ λΉκ΅ν΄μΌνλ λ²κ±°λ‘μμ΄ μμκ³ , λΉκ΅νλ κ³Όμ μμ μ€μλ μΌμ΄λ μ μμ΄ μνν μ½λλΌκ³ μκ°νλ€.
β ν΄κ²° κ³Όμ
μ¬μ€ νμ¬ ν΄λμ€μμλ§ μ΄λ° κΈ°λ₯μ΄ μ‘΄μ¬νλ€λ©΄ μ λ°©μμΌλ‘ ꡬμ±ν΄λ ν¬κ² λ¬Έμ κ° μλ€. νμ§λ§ λλ¦ κ°λ°μλ‘μ λ€λ₯Έ κ³³μμλ μ¬μ©ν μ μλλ‘ λ§λ€κ³ μΆμ μμ¬μ΄ μκ²Όκ³ , λ ν¨μ¨μ μΌλ‘ μ²λ¦¬ν μ μμκΉμ λν κ³ λ―Όμ νκ² λμλ€.
1. μꡬ λΆμ
νμ¬ λ΄κ° λ§λλ €κ³ νλ κΈ°λ₯μ΄ λ¬΄μμ νμλ‘ νλμ§ λΆμνλ€.
- λ³κ²½λ νλ‘νΌν°μ λ μ΄λΈ
- μ) title -> μ λͺ©
- λ³κ²½λ νλ‘νΌν°μ κ°
- μ) Boolean -> μ¬μ© ν¨ / μ¬μ©νμ§ μμ || μμ λ¨ / μμ λμ§ μμ
μ΄κ²λ§ λ΄λ λ²μ¨ 볡μ‘νλ€. Boolean νμ μ΄λλΌλ λμΌν λ μ΄λΈμ μ¬μ©νμ§ λͺ»νκ³ , ν΄λΌμ΄μΈνΈκ° μνλ κ°μ λ£μ μ μλλ‘ ν΄μΌ νλ€. λν, κΈ°ν μλμ μν΄ λ³κ²½λ κ°λ€μ΄ 보μ¬μ§ μμλ μ§μ ν΄μ€ μ μμ΄μΌ νλ€.
2. ν λ§λ€κΈ°
μ°μ , ν΄λΌμ΄μΈνΈμμ μλ ν΄λμ€μ λν 리μ€νΈλ₯Ό 보λ΄μ€λ€κ³ κ°μ νμ.
data class Item(
val order: Int,
val propertyName: String,
val label: String,
val v1: String = "",
val v2: String = ""
)
val items = listOf(
Item(1, "title", "μ λͺ©"),
Item(2, "writer", "μμ±μ", origin.writer.username, changed.writer.username),
Item(3, "category", "μΉ΄ν
κ³ λ¦¬"),
Item(4, "isDeleted", "μμ μ¬λΆ", "μμ λ¨", "μμ λμ§ μμ")
)
findChanges(items)
ν΄λΉ μ 보λ₯Ό ν λλ‘ λ¦¬νλ μ μ μ¬μ©ν΄ μ λ€λ¦μΌλ‘ λ€μ΄μ¨ ν΄λμ€μ λν μ 보λ₯Ό λ°κ³ , ν΄λΉ ν΄λμ€κ° κ°κ³ μλ κ°κ°μ νλλ₯Ό μννλ©°, ν΄λΌμ΄μΈνΈκ° 보λ΄μ€ νλμ μΌμΉνλμ§ λΉκ΅ν κ²μ΄λ€.
μ΄μ ν΄λμ€ λ΄λΆμ λ€λ₯Έ κ°μ²΄λ₯Ό μ°Έμ‘°νκ³ μλ κ²½μ°λ μμΌλ, μ νν λΉκ΅λ₯Ό ν μ μλλ‘ equals, hashCodeλ₯Ό ꡬννκ² κ°μ ν΄λ³΄μ.
interface Detectable {
override fun hashCode(): Int
override fun equals(other: Any?): Boolean
}
class Post(
val title: String = "",
val content: String = "",
val isDeleted: Boolean = false,
val category: PostCategory = PostCategory.C1,
val writer: Member
) : Detectable {
override fun equals(other: Any?): Boolean {
...
}
override fun hashCode(): Int {
...
}
}
μμ κ°μ΄ Detectable
μΈν°νμ΄μ€λ₯Ό ꡬννλλ‘ νλ©΄, λ΄λΆμ λ€λ₯Έ κ°μ²΄λ₯Ό μ°Έμ‘°νκ³ μλλΌλ μ νν λΉκ΅λ₯Ό ν μ μκ² λλ€.
3. ꡬν
μμ μ€λͺ
ν κ²κ³Ό κ°μ΄ 리νλ μ
μ μ¬μ©ν΄ μ λ€λ¦μ λν ν΄λμ€λ₯Ό λ°μμ¬ κ²μ΄λ€. κ·Έλ¬κΈ° μν΄μλ λ°νμμλ νμ
μ λ³΄κ° μ΄μμμ΄μΌ νκΈ° λλ¬Έμ inline
ν¨μμ reified
λ₯Ό μ¬μ©ν κ²μ΄λ€.
/**
* λ κ°μ²΄λ₯Ό λΉκ΅ν΄ λ³κ²½λ λΆλΆμ μ°Ύλ ν¨μ
* @param origin κΈ°μ‘΄ κ°μ²΄
* @param target λΉκ΅ λμ κ°μ²΄
* @param itemList λ³κ²½μ κ°μ§ν μμ΄ν
리μ€νΈ
* */
inline fun <reified T : Detectable> findChanges(
origin: T,
target: T,
itemList: List<Item>
): List<Item> { ... }
inline
ν¨μλ₯Ό μ¬μ©νλ©΄, ν¨μ νΈμΆλΆκ° ν¨μ λ³Έλ¬ΈμΌλ‘ λμΉλλ€. λλΆμ reified
λ₯Ό μ¬μ©ν΄ λ°νμμλ μ λ€λ¦ νμ
μ 보λ₯Ό κ°κ³ μμ μ μκ² λλ€. λ§μ½ μΌλ° ν¨μμ reified
ν€μλλ₯Ό μ¬μ©νμ§ μμ κ²½μ°, λ€μκ³Ό κ°μ μλ¬κ° λ°μνλ€.
Cannot use 'T' as reified type parameter. Use a class instead.
μ λ€λ¦μ λ°νμμ νμ
μ λ³΄κ° μκ±°λκΈ° λλ¬Έμ νμ
μ 보λ₯Ό μ€μ²΄ν ν΄μ£ΌκΈ° μν΄ reified
ν€μλλ₯Ό μ¬μ©ν΄μΌ νλ€. ν΄λΉ ν€μλλ inline
ν¨μμμ μ¬μ©μ΄ κ°λ₯νλ€. λν, T
λ κΈ°λ³Έμ μΌλ‘ Any?
λ₯Ό μνμΌλ‘ μ§μ νκΈ° λλ¬Έμ Detectable
μ ꡬνν ꡬνμ²΄λ§ λ€μ΄μ¬ μ μλλ‘ νλ€.
Kotlin in action 8μ₯(κ³ μ°¨ ν¨μ; νλΌλ―Έν°μ λ°ν κ°μΌλ‘ λλ€ μ¬μ©)μ λν λ΄μ©μ 보면,
inline
ν¨μλ λλ€μ μ¬μ©ν λλ§ μ¬μ©νλ κ²μ μΆμ²νλ€κ³ νλ€. νμ§λ§ ν΄λμ€λ‘ λ§λ€κΈ°μ μ λ§€νλ μ°μ μ§ννλλ‘ νκ² λ€.
μ΄μ ...
μ λν λ΄μ©μ ꡬνν΄λ³΄μ.
// μ€μ²΄νλ T νμ
μ ν΄λμ€ μ 보 λ°μμ€κΈ°
val clazz = T::class // Type: KClass<T>
// κ²°κ³Ό 리μ€νΈλ₯Ό λ΄μ Item 리μ€νΈ
val result = mutableListOf<Item>()
for (property in clazz.memberProperties) {
val propertyName = property.name
itemList.find { it.propertyName == propertyName }?.let {
...
}
}
μμ κ°μ΄ clazz
κ° κ°κ³ μλ λ©€λ² νλ‘νΌν°λ€μ κ°μ Έμ iterator
λ₯Ό λλ €μ£Όκ³ , ν΄λΉ νλ‘νΌν°μ μ΄λ¦μ΄ ν΄λΌμ΄μΈνΈκ° 보λ΄μ€ itemList
μ ν¬ν¨λμ΄ μλμ§ κ²μ¬ν΄μ€λ€. λ§μ½ ν¬ν¨μ΄ λμ΄μμ κ²½μ° κ°μ λν λΉκ΅λ₯Ό μ§νν΄μ£ΌκΈ°λ§ νλ©΄ λλλ€.
// κΈ°μ‘΄ κ°μ²΄μ κ°
val originValue = property.get(origin).toString()
// νκ² κ°μ²΄μ κ°
val targetValue = property.get(target).toString()
// λ κ°μ΄ λ€λ₯Έμ§ λΉκ΅
if (originValue != targetValue) {
// item κ°μ²΄μ v1μ΄ λΉμ΄μμ κ²½μ° originValueλ₯Ό κ·Έλλ‘ μ¬μ©
val v1 = it.v1.ifEmpty { originValue }
// item κ°μ²΄μ v2κ° λΉμ΄μμ κ²½μ° targetValueλ₯Ό κ·Έλλ‘ μ¬μ©
val v2 = it.v2.ifEmpty { targetValue }
// κ²°κ³Ό 리μ€νΈμ μΆκ°
result += Item(it.order, it.propertyName, it.label, v1, v2)
}
κΈ°μ‘΄μλ λ΄κ° λͺ¨λ νλλ₯Ό λΉκ΅ν΄μΌ νμ§λ§, μ΄μ λ λ€μκ³Ό κ°μ΄ κ°λ¨νκ² λ¦¬μ€νΈλ§ λ§λ€μ΄μ ν¨μλ₯Ό νΈμΆνκΈ°λ§ νλ©΄ λ κ°μ²΄μ κ°μ μ½κ² λΉκ΅ν μ μλ€.
val items = listOf(
Item(1, "title", "μ λͺ©"),
Item(2, "writer", "μμ±μ", origin.writer.username, changed.writer.username),
Item(3, "category", "μΉ΄ν
κ³ λ¦¬"),
Item(4, "isDeleted", "μμ μ¬λΆ", "μμ λ¨", "μμ λμ§ μμ")
)
μ 체 μ½λμ κ²°κ³Ό κ°μ μλμ κ°λ€.
/**
* λ κ°μ²΄λ₯Ό λΉκ΅ν΄ λ³κ²½λ λΆλΆμ μ°Ύλ ν¨μ
* @param origin κΈ°μ‘΄ κ°μ²΄
* @param target λΉκ΅ λμ κ°μ²΄
* @param itemList λ³κ²½μ κ°μ§ν μμ΄ν
리μ€νΈ
* */
inline fun <reified T : Detectable> findChanges(
origin: T,
target: T,
itemList: List<Item>
): List<Item> {
val clazz = T::class
val result = mutableListOf<Item>()
for (property in clazz.memberProperties) {
val propertyName = property.name
itemList.find { it.propertyName == propertyName }?.let {
val originValue = property.get(origin).toString()
val targetValue = property.get(target).toString()
if (originValue != targetValue) {
val v1 = it.v1.ifEmpty { originValue }
val v2 = it.v2.ifEmpty { targetValue }
result += Item(it.order, it.propertyName, it.label, v1, v2)
}
}
}
return result.sortedBy { it.order }
}
// κ²°κ³Ό (titleμ κ°μ΄ λμΌνκΈ° λλ¬Έμ κ²°κ³Ό 리μ€νΈμ μΆκ°λμ§ μμ)
[Item(order=2, field=category, label=μΉ΄ν
κ³ λ¦¬, v1=μμ κ²μν, v2=μ 보 κ²μν),
Item(order=3, field=isDeleted, label=μμ μ¬λΆ, v1=μμ λ¨, v2=μμ λμ§ μμ),
Item(order=4, field=writer, label=μμ±μ, v1=admin, v2=tester)]
π€ νκ³
μ½νλ¦°μ μμνμ§ 2λ¬ μ λμΈμ§λΌ λ§μ΄ λ―Έμνλ€. μ¬μ€ μ²μμ λΉκ΅ν λ°©μμ O(N)
λ§νΌμ 볡μ‘λλ₯Ό κ°μ§μ§λ§, λ³κ²½ν μ½λλ O(N^2)
λ§νΌμ 볡μ‘λλ₯Ό κ°μ§λ€. λλ¬Έμ μ²μμ λΉκ΅ν λ°©μμ΄ μ±λ₯μ μΌλ‘ λ μ’μ§λ§, νλνλ λΉκ΅λ₯Ό νλ€λ κ²μ λΆνΈνκΈ° λλ¬Έμ μ΄μ λμ μ±λ₯ μ°¨μ΄λ κ°μν΄μΌν κ² κ°λ€. μ΄λ³΄λ€ λ ν¨μ¨μ μΌλ‘ λ§λ€ μ μμμ§ λ κ³ λ―Όν΄λ³΄κ³ , 리ν©ν°λ§νλ μκ°μ κ°μ ΈμΌκ² λ€!
λ νΌλ°μ€
- Kotlin In Action - Chapter08; κ³ μ°¨ ν¨μ; νλΌλ―Έν°μ λ°ν κ°μΌλ‘ λλ€ μ¬μ©
- Kotlin In Action - Chapter09; μ λ€λ¦μ€