-
Kotlin으로 깔끔한 Builder를 만들어보자개발/안드로이드 2020. 4. 14. 23:59
Kotlin에서 제공하는 apply 범위 함수를 이용해서 클래스 내부 속성 값을 간결하게 선언할 수 있지만 DSL(Domain Specific Language) 언어인 점을 응용하면 여러 클래스를 중첩한 클래스의 속성값에 대해서 더욱 간결하게 값을 설정 할 수 있다. 얼마나 간결한지 글로 길게 설명하는 것 보다는 간단한 예시로 보는게 좋을 것 같다.
Kotlin의 Builder 패턴을 사용하면 아래와 같이 선언된 data 클래스들을
data class Group( val name: String, val company: Company, val members: List<Member> ) data class Company( var name: String = "" ) data class Member( val name: String, val alias: String, val year: Int )
이렇게 선언 하는 것이 가능하다.
val redVelvet = group { name { "레드벨벳" } company { name { "SM Entertainment" } } members { member { name { "슬기" } alias { "곰슬기" } year { 1994 } } member { name { "아이린" } alias { "얼굴 천재" } year { 1991 } } member { name { "웬디" } alias { "천사" } year { 1994 } } } }
이렇게 간결하게 코드를 만들기 위해선 각각의 클래스에 대해서 Builder 클래스와 lambda 함수를 사용한 내부에 셋팅 함수를 선언해야한다. Member 함수부터 구현 방법을 살펴보자.
1. MemberBuilder
class MemberBuilder { private var name: String = "" private var alias: String = "" private var year: Int = 0 fun name(lambda: () -> String) { name(lambda) } fun alias(lambda: () -> String) { alias(lambda) } fun year(lambda: () -> Int) { year(lambda) } fun build() = Member(name, alias, year) }
MemberBuilder 클래스 내부에는 Member 데이터 클래스와 동일하나 name, alias, year 를 변수로 가지는데 보면 셋팅하는 함수들의 인자가 lambda로 선언되어 있고 바로 내부 변수를 초기화해준다는 점만 다르다. lambda가 포함된 함수는 아까 레드벨벳 초기화 코드에서 확인 할 수 있듯이 간단히 primitive 인자값을 전달하는 방식 만으로도 선언이 가능하다. build() 함수는 현재까지 초기화된 정보로 Member 클래스를 만드는 작업이다. 다른 곳에서 호출 받게 된다.
2. MemberListBuilder
class MemberListBuilder { private val employeeList = mutableListOf<Member>() fun member(lambda: MemberBuilder.() -> Unit) = employeeList.add(MemberBuilder().apply(lambda).build()) fun build() = employeeList }
Group의 데이터 클래스에 Member가 리스트 형태로 선언돼있기 때문에 Member의 개수는 1개 이상이 될 수 있다. 그래서 복수의 Member에 대해서 처리할 수 있는 MemberListBuilder가 이 부분을 담당한다. 레드벨벳 초기화 코드에서 member { ... } 로 호출한 부분은 바로 이 MemberListBuilder 클래스의 내부 함수를 호출한 것이다. 함수 내부를 보면 받아온 정보를 가지고 바로 MemberBuilder() 클래스 내부의 build() 함수를 통해 멤버를 생성하고 내부 배열 변수(employeeList)에 추가한다. build() 함수에서는 가지고 있는 배열 정보를 리턴한다.
3. CompanyBuilder
class CompanyBuilder { private var name = "" fun name(lambda: () -> String) { this.name = lambda() } fun build() = Company(name) }
CompanyBuilder는 말단 노드라 MemberBuilder랑 생긴게 거의 비슷하다. 굳이 다시 한번 설명하지 않아도 될 것 같다. lambda 인자로 값을 받고 build() 에서 현재 클래스 값을 전달한다.
4. GroupBuilder
class GroupBuilder { private var name = "" private var company = Company("") private val employees = mutableListOf<Member>() fun name(lambda: () -> String) { name = lambda() } fun company(lambda: CompanyBuilder.() -> Unit) { company = CompanyBuilder().apply(lambda).build() } fun members(lambda: MemberListBuilder.() -> Unit) = employees.addAll(MemberListBuilder().apply(lambda).build()) fun build() = Group(name, company, employees) }
GroupBuilder 클래스에는 지금까지 만들어왔던 builder를 포함하고 있다. name 함수에서는 Group의 이름을 정하고, company 함수에서는 CompanyBuilder를 통해서 company 속성값 정보를 셋팅한다. members 함수에서도 마찬가지로 MemberListBuilder 클래스를 통해 현재 입력된 모든 멤버의 정보를 입력한다. GroupBuilder() 또한 build() 함수를 호출해서 현재 Group 클래스를 최종적으로 반환한다.
GroupBuilder 클래스의 build() 함수를 효출하는 부분은 따로 함수를 만들어야하는데 이렇게 만들면 된다.
fun group(lambda: GroupBuilder.() -> Unit): Group { return GroupBuilder().apply(lambda).build() }
선언부에서 알 수 있듯이 가장 먼저 호출한 함수는 group {..} 이었다.
전체 코드는 다음과 같다.
Builder 패턴 구현 부분 코드
데이터 초기화 부분 코드
'개발 > 안드로이드' 카테고리의 다른 글
안드로이드 그림자(Shadow) 효과 넣기 (1) 2020.04.18 Kotlin - Coroutine (0) 2020.04.15 Exoplayer2 사용하기 (0) 2020.04.12 FragmentManagers Android (1) 2020.04.06 ViewModelProviders.of deprecated (0) 2020.04.06