— kotlin, programming — 1 min read
상수값이 그대로 코드에 박혀서 반복적으로 사용된다면 문제가 될수 있습니다.
1// password validation 함수2// before3fun isPasswordValid(text: String): Boolean {4 if(text.length < 7) return false5 // ...6}7
8// after9const val MIN_PASSWORD_LENGTH = 710
11fun isPasswordValid(text: String): Boolean {12 if(text.length < MIN_PASSWORD_LENGTH) return false13 // ...14}
다음으로 예시를 통해서 함수를 이용한 추상화를 알아보자.
유저들에게 토스트 메시지를 띄워주는 기능을 추가하려고 한다고 해보자
1// extenstion function을 이용한 한단계 추상화2// before3Toast.makeText(this, message, Toast.LENGTH_LONG).show4
5// after6fun Context.toast(7 message: String,8 duration: Int = Toast.LENGTH_LONG9) {10 Toast.makeText(this, message, duration).show()11}12
13context.toast(message)
common algorithm을 빼는 것은 여러 이점을 준다 (참고)
하지만 위의 코드에서 대비할수 없는 변화들이 있다. 예를 들어, toast message로 보여주던 부분들을 snackbar message 형태로 변경해야 한다고 하자.
가장 쉽게 생각할수 있는 대응은 내부구현을 snackbar를 보여주도록 변경하고 함수의 이름을 변경하는 것이다.
1fun Context.snackbar(2 message: String,3 duration: Int = Snackbar.LENGTH_LONG4) {5 // ...6}
하지만, 이 같은 대응은 여러 문제들을 초래하게 됩니다. 그 중 가장 큰 문제는 사용하는 모든 쪽에서의 변경이 불가피하다는 것입니다. 따라서 이러한 문제를 해결하기 위해 한단계 높은 함수를 사용하여 추상화를 할 수 있습니다.
1fun Context.showMessage(2 message: String,3 duration: MessageLength = MessageLength.LONG4) {5 val toastDuration = when(duration) {6 SHORT -> Toast.LENGTH_SHORT7 LONG -> Toast.LENGTH_LONG8 }9 Toast.makeText(this, message, toastDuration).show()10}11
12enum class MessageLength { SHORT, LONG }
실제 구현부분은 숨겨둠으로서, 좀 더 변화에 강한 코드를 만들수 있습니다. snackbar 형태로 변경하려 한다면, 내부 구현을 변경하는 것 만으로 할 수 있습니다.
위에서 보여주는 가장 큰 변화는 네이밍입니다. 함수라는 것 자체는 추상화를 표현하는 것이기 때문에 이름은 굉장히 중요합니다. 따라서, 그에 맞는 이름을 붙여주는 것이 중요합니다.
하지만, 여기에서도 한계가 있습니다. 함수라는 것 자체는 상태를 가질수 없습니다. 따라서 더 높은 추상화를 위해서 class를 사용하는 법을 알아봅시다.
1class MessageDisplay(val context: Context) {2 3 fun show(4 message: String,5 duration: MessageLength = MessageLength.LONG6 ) {7 val toastDuration = when(duration) {8 SHORT -> Toast.LENGTH_SHORT9 LONG -> Toast.LENGTH_LONG10 }11 Toast.makeText(this, message, toastDuration).show()12 }13}14
15// usage16val messageDisplay = MessageDisplay(context)17messageDisplay.show("message")
class는 function 보다 더 강력한 추상화를 표현할수 있는데 이에는 몇가지 이유가 있다.
또한 class 추상화의 이점들도 있습니다.
하지만 클래스 추상화도 한계점이 있습니다. final class는 정확한 타입아래에 있기 때문에 정확한 구현을 알게 됩니다. 오픈 클래스의 경우에는 하위 클래스에 위임할수 있기때문에 좀더 유연하지만, 해당 클래스와 강하게 결합되어있습니다.
따라서 우리는 interface를 사용해 더 높은 추상화를 이룰수 있습니다.
코틀린의 stdlib은 대부분 interface로 표현됩니다.
이러한 이유는
1interface MessageDisplay {2 fun show(3 message: String,4 duration: MessageLength = MessageLength.LONG5 )6}7
8class ToastDisplay(val context: Context): MessageDisplay {9 10 override fun show(11 message: String,12 duration: MessageLength = MessageLength.LONG13 ) {14 val toastDuration = when(duration) {15 SHORT -> Toast.LENGTH_SHORT16 LONG -> Toast.LENGTH_LONG17 }18 Toast.makeText(this, message, toastDuration).show()19 }20}
인터페이스를 이용해서 더 많은 자유도를 얻었습니다.
결론적으로 사용과 선언의 결합도를 낮추게 되었습니다. 그로 인해, 좀더 변화에 유연한 구조를 가지게 되었습니다.
하지만, interface를 변경하게 되는 일이 생긴다면, 모든 implement 하고 있는 class들을 변경해야한다는 단점이 있습니다.