- HomeModelRecyclerAdapter의 구현이 꼭 필요했어요?
1-1. 왜 필요하다고 생각을 했는가?
1-2. 필요하다고 치더라도, RecyclerAdapter class의 개수를 늘려가면 inner class로 구현을 하는것과 큰 차이가 없다고 생각을 하는데, 어떻게 생각을 하는가?
1-3. 만약에 저렇게 해도 합당하다면, 나중에 어댑터 클래스를 관리하는데 있어서 어려움이 예상되지는 않는가? (만약에 우리 코드를 처음 보는 사람이 보더라도 의문점을 가지지 않을까?)- 굳이 TownMarketModel, HomeItemModel을 HomeListModel이라는 추상 클래스로 관리를 해야해요?
2-1. Model도 companion object로 Diff_Util을 선언하고 있고, HomeListModel도 companion object로 Diff_Util을 선언하고 있는데, Model로 통합 관리하는게 맞지 않을까?
- Diff_Util에 관해서 이해는 하고 계실까요?
- Dependency Injection에 관해서 제대로 이해는 하고 계시나요?
첫번째 코드 리뷰를 한지 이제 2주가 된것 같은데, 제가 받은 질문에 대한 글을 이제야 쓰게 되었네요 ㅠㅠ(게으름이 죄입니다... 😥) 사실 최근에는 지금 진행중인 YUMarket의 Back-End 파트를 개발하느라 정신이 없기도 하였고...하하;; 핑계대지마
어쨌든 제가 첫번째 코드 리뷰를 진행하면서 받았던 질문들에 대해서 작성해보도록 하겠습니다!
사실 이 질문이 두 번째 질문과 연관이 깊은 질문인데, 은근슬쩍 두 번째 질문에 대한 답변 작성 회피하기 제가 이전 글에서 작성하였듯이, 우리가 구현한 RecyclerAdapter의 경우에는 ListAdapter(Model.DIFF_CALLBACK)을 상속받고 있기 때문에 DIFF_CALLBACK에서 정의한 비교 기준에 따라서 아이템을 비교하여 리스트의 item 변경이 발생하면 그 부분만 submit에서 갈아끼워 준답니다! 그래서 참... 비교 기준이라는 관점에서 바라보게 된다면 HomeListCategory까지 같이 비교해줄 필요가 있었기 때문에 DIFF_CALLBACK을 따로 구현해줄 필요가 있을거라고 생각을 했어요.
그렇게 되면 Model의 DIFF_CALLBACK과 HomeListModel의 DIFF_CALLBACK은 전혀 다른 놈(?)이 되어버리기 때문에, 기존의 ModelRecyclerAdapter를 사용할수가 없었습니다. (일단, ListAdapter에 전달해야할 DIFF_CALLBACK의 소속 class가 달라지기 때문에, ListAdapter의 제네릭 파라미터도 Model에서 HomeListModel로 바뀌어야하고, 그렇게 되면 ModelRecyclerAdapter를 사용하지 못한다는 결론에 이르게 된답니다.
그러면, 해결 방법은 두가지가 있어요. 해결 방법은 다음과 같답니다!
- DIFF_CALLBACK을 어떻게든 통합을 시켜서 TownMarketModel, HomeItemModel을 Model을 상속받도록 관리를 시행한다. (사실 이게 채택이 되었답니다!)
- 그냥 모든 enum을 한 군데서 관리를 해버려서 Model을 상속받도록 해버리면 모든게 해결이 된다.
이 방법들에 대한 고찰은 이후에 하도록하고, 그 다음 받았던 질문을 소개할게요!
사실 제가 했었던 답은 간단했습니다.
RecyclerAdapter의 개수는 abstract model의 개수에 비례를 하기 때문에 우려할만큼 RecyclerAdapter의 개수가 증가하지 않을것이다. 따라서 괜찮다.
그런데 제가 구현을 했던 방법이 틀렸음이 증명되었는데, 저의 팀원 한 분이 DIFF_CALLBACK을 따로 관리해야하는 문제를 해결해주었답니다! (감사합니다...그저 빛 😍)
일단 코드부터 볼까요?
Model 클래스
open fun isTheSame(item: Model) =
this.id == item.id && this.type == item.type
companion object {
val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Model>() {
override fun areItemsTheSame(oldItem: Model, newItem: Model): Boolean {
return oldItem.isTheSame(newItem)
}
override fun areContentsTheSame(oldItem: Model, newItem: Model): Boolean {
return oldItem == newItem
}
}
}
HomeItemModel 클래스
override fun isTheSame(item: Model) =
if (item is HomeItemModel) {
super.isTheSame(item) && this.homeListCategory == item.homeListCategory
} else {
false
}
DIFF_CALLBACK의 areItemsTheSame 메소드에서 비교를 수행하는 부분을 바깥으로 빼서 open method로 선언을 해주었어요. 사실 companion object의 경우에는 Model과는 별개의object이기 때문에 바깥으로 method를 빼주면 안 될것 같지만, 되는 이유는 다음과 같답니다!
areItemsTheSame(oldItem: Model, newItem: Model) 메소드를 자세히 보면, 메소드의 파라미터가 Model 타입을 받고있기 때문에, Model class의 메소드인 isTheSame을 사용할 수 있다!
그리고 그 외에, Town_Market, Home_Item 타입을 CellType으로 enum을 통합시켜서 기존에 발생하던 자잘한 오류들을 해결하여, TownMarketModel, HomeItemModel을 Model을 상속받을 수 있도록 손을 보았답니다!
결국에는, HomeModelRecyclerAdapter와 HomeModelViewHolder를 기존에 존재하던 코드들로 통합을 할 수 있었다는것! 결국에 내 코드는 삽질이었던걸로...
[참고] https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil
[참고] https://developer.android.com/reference/kotlin/androidx/recyclerview/widget/ListAdapter
DiffUtil에 대한 개념자체는 아주 간단해요! 사용자가 DiffUtil의 ItemCallBack에 선언해둔 비교 기준에 따라서 리스트의 아이템을 비교해주는 도구(?)라고 생각을 해주면 적절할 것 같아요!
그런데, DiffUtil을 왜 RecyclerAdapter에 적용을 하게 되었는가? 에 대한 해답도 하고 넘어가야할 것 같아요. 그 이유는 다음과 같답니다!
기존의 RecyclerAdapter는 RecyclerView.Adapter를 상속받아서 구현을 하였는데, 여기서는 리스트에 변화가 생기면 따로 submit에 대한 알고리즘을 작성하여 리스트를 갈아끼우거나 하는 방식으로 구현을 했어야하는데, 결국에는 리스트를 통째로 갈아끼우는 대참사(?)가 발생하고 있었어요.
그래서 후에는 RecyclerView.Adapter를 상속받는 ListAdapter를 안드로이드에서 제공을 하여서, 생성자 파라미터로 DIFF_UTIL을 받게하여 리스트 교체에 대한 오버헤드(부담)를 줄여줬습니다!
참고적으로, 위 사이트에서는 Diff_Util에 대한 성능도 제시를 하고있는데, 참고를 해주셨으면 좋겠습니다! 대충 DiffUtil 만세라는 뜻
일단 예시부터 볼까요?
Calculator interface 및 구현체
interface Calculator {
fun plus(num1: Int, num2: Int): Int
fun minus(num1: Int, num2: Int): Int
}
class DollorCalculator: Calculator {
private val dollorToWon = 1200
override fun plus(num1: Int, num2: Int) = dollorToWon * (num1 + num2)
override fun minus(num1: Int, num2: Int) = dollorToWon * (num1 - num2)
}
class GoldCalculator: Calculator {
private val goldToWon = 1000000
override fun plus(num1: Int, num2: Int) = goldToWon * (num1 + num2)
override fun minus(num1: Int, num2: Int) = goldToWon * (num1 - num2)
}
Person class
class Person(private val calculator: Calculator) {
fun printPlusResult(num1: Int, num2: Int) {
println(calculator.plus(num1, num2))
}
fun printMinusResult(num1: Int, num2: Int) {
println(calculator.minus(num1, num2))
}
}
예시를 간단하게 들어보았는데요, Person class의 경우 생성자로 calculator를 받아서 plus, minus의 결과를 출력해주는 역할을 수행하도록 하였습니다.
위의 Person class를 관찰해보면 외부에서 calculator를 구현한 구현체를 받아서 기능을 수행해야만 하는 구조를 가지는데요, 이러한 모습을 Person class는 Calculator에 대한 의존성을 가지며, Person class는 Calculator interface에 대한 의존성을 주입받는다 라고 흔히 표현을 해줍니다.
의존성 주입에 대한 자세한 내용은 추후의 포스트에서 자세하게 다뤄보도록 하겠습니다. (사실 의존성 주입에 대해서 해야할 말이 너무 많아요. 그래서 글을 분리해주는게 맞다고 생각합니다!)
이상으로 박카스 팀의 첫번째 코드 리뷰 포스트를 마치겠습니다! 이후에 프로젝트를 진행하면서 글을 추가적으로 올려보도록 하겠습니다.
긴글 읽어주셔서 감사합니다. 질문은 언제나 환영입니다!