개발/최적화

case를 효과적으로 다루는 방법

이도일 2022. 10. 7. 09:11

시간이 난 김에 정리를 해본다.

 

개발자라면 거의 매일 마주하게 되는 고민......바로 case를 분기하는 시점을 어떻게 다룰것이냐 하는 것.

가장 베이직한 if/else도 있고, when, switch 등등......

오늘은 이걸 효과적으로 다루는 방법 중 하나인 다형성에 대해서 정리해봤다.

 

 

참고 글은 이거

 

Avoid Using “when” Expression as Much as Possible. Use Polymorphism Instead

A tip for Android developers

betterprogramming.pub

 

윗 글 내용을 내가 쓴것처럼..막 써보겠다.

 

 

 

 

아래와 같은 케이스를 가정해보자

sealed class ButtonSize {
    object Small : ButtonSize()
    object Medium : ButtonSize()
    object Large : ButtonSize()
    object Huge : ButtonSize()
    data class Custom(val heightDpInt: Int) : ButtonSize()
}

@Composable
fun RenderButton(
    buttonSize: ButtonSize,
    onButtonClick: () -> Unit,
    text: String
) {
    Button(
        modifier = Modifier.height(buttonSize.getButtonHeight()).fillMaxWidth(),
        onClick = onButtonClick,
        content = { Text(text = text) }
    )
}

private fun ButtonSize.getButtonHeight(): Dp {
    return when (this) {
        ButtonSize.Small -> 16.dp
        ButtonSize.Medium -> 24.dp
        ButtonSize.Large -> 32.dp
        ButtonSize.Huge -> 40.dp
        is ButtonSize.Custom -> this.heightDpInt.dp
    }
}

이런 케이스의 경우, dp라는 단위가 선언시에 계속 생성되며,

이미 뷰모델이 어떤 버튼을 사용할지 알고 있는데도.... 추가 런타임이 발생하게 된다.

 

 

 

 

따라서 이걸 해결하기 위해서는, 아래와 같이

/*
 * Here we moved dp values from getButtonHeight method.
 * getButtonHeight method has been removed.
 */
sealed class ButtonSize(open val heightDpInt: Int) {
    object Small : ButtonSize(16)
    object Medium : ButtonSize(24)
    object Large : ButtonSize(32)
    object Huge : ButtonSize(40)
    data class Custom(override val heightDpInt: Int): ButtonSize(heightDpInt)
    
    fun getHeightDp(): Dp {
      return  heightDpInt.dp
    }
}

@Composable
fun RenderButton(
    buttonSize: ButtonSize,
    onButtonClick: () -> Unit,
    text: String
) {
    Button(
        modifier = Modifier.height(buttonSize.getHeightDp()).fillMaxWidth(),
        onClick = onButtonClick,
        content = { Text(text = text) }
    )
}

이렇게 다형성을 이용해,

하나의 ButtonSize 클래스를 크기가 다른 버튼들에서 사용할 수 있게 하고,

getHeightDp를이용해 크기만 리턴하게 해주면 된다. 굿.

 

 

 

아래는 조금 더 복잡한 예문이다.

 

 

 

 

1) 다형성을 사용해, 크기와 색, 텍스트가 다른 버튼을 만들었을 때.

sealed class ButtonSize(
    open val heightDpInt: Int,
    open val color: Color,
    open val textResId: Int
) {
    object Small : ButtonSize(16, Color.Red, R.string.small_button_text)
    object Medium : ButtonSize(24, Color.Gray, R.string.medium_button_text)
    object Large : ButtonSize(32, Color.Green, R.string.large_button_text)
    object Huge : ButtonSize(40, Color.Blue, R.string.huge_button_text)
    data class Custom(
        override val heightDpInt: Int,
        override val color: Color,
        override val textResId: Int
    ) : ButtonSize(heightDpInt, color, textResId)
    
    fun getHeightDp(): Dp {
        return heightDpInt.dp
    }
}

@Composable
fun RenderButton(
    buttonSize: ButtonSize,
    onButtonClick: () -> Unit
) {
    Button(
        modifier = Modifier
            .height(buttonSize.getHeightDp())
            .fillMaxWidth(),
        onClick = onButtonClick,
        colors = ButtonDefaults.buttonColors(
            backgroundColor = buttonSize.color,
        ),
        content = {
            Text(text = stringResource(buttonSize.textResId))
        }
    )
}

요것만 보면 위의 코드랑 크게 차이 없어보이지만....

 

 

 

2) when 표현식을 이용해 크기와 색, 텍스트가 다른 버튼을 만들었을 때

sealed class ButtonSize() {
    object Small : ButtonSize()
    object Medium : ButtonSize()
    object Large : ButtonSize()
    object Huge : ButtonSize()
    data class Custom(
        val heightDpInt: Int,
        val color: Color,
        val textResId: Int
    ) : ButtonSize()
}

@Composable
fun RenderButton(
    buttonSize: ButtonSize,
    onButtonClick: () -> Unit
) {
    Button(
        modifier = Modifier
            .height(buttonSize.getButtonHeight())
            .fillMaxWidth(),
        onClick = onButtonClick,
        colors = ButtonDefaults.buttonColors(
            backgroundColor = buttonSize.getButtonColor(),
        ),
        content = {
            Text(text = stringResource(buttonSize.getButtonText()))
        }
    )
}

private fun ButtonSize.getButtonHeight(): Dp {
    return when (this) {
        ButtonSize.Small -> 16 // Code duplication
        ButtonSize.Medium -> 24 // Code duplication
        ButtonSize.Large -> 32 // Code duplication
        ButtonSize.Huge -> 40 // Code duplication
        is ButtonSize.Custom -> this.heightDpInt // Code duplication
    }
}

private fun ButtonSize.getButtonText(): Int {
    return when (this) {
        ButtonSize.Small -> R.string.small_button_text // Code duplication
        ButtonSize.Medium -> R.string.medium_button_text // Code duplication
        ButtonSize.Large -> R.string.large_button_text // Code duplication
        ButtonSize.Huge -> R.string.huge_button_text // Code duplication
        is ButtonSize.Custom -> this.textResId // Code duplication
    }
}

private fun ButtonSize.getButtonColor(): Color { 
  return when (this) {
        ButtonSize.Small -> Color.Red // Code duplication
        ButtonSize.Medium -> Color.Gray // Code duplication
        ButtonSize.Large -> Color.Green // Code duplication
        ButtonSize.Huge -> Color.Blue // Code duplication
        is ButtonSize.Custom -> this.color // Code duplication
    }
}

훨씬 길어짐.

그리고 2의 경우에는 나중에 다른 사이즈의 버튼을 추가해야할때...(기능을 확장할 때)

텍스트, 색, 크기 변환 함수에 각각 하나의 케이스를 추가해야하지만, 1의 경우는 코드 한 줄이면 끝난다.