본문 바로가기

MY아이디어

[아이디어] Switch문 최대한 안쓰기

스프링에서 타입에 따라 다른 행위나 결과값을 리턴해야할 경우, Switch (코틀린은 When)를 사용하여 코딩을 한다...

 

아래와 같은 코드가 있다고 해볼까?


@Service
class TestService {

fun printCardType(cardType:CardType) {
        when (cardType) {
            CardType.A -> println("A")
            CardType.B -> println("B")
            else -> println("UNKNOWN")
        }
    }
}

타입이 추가되면, 해당 TestService의 printCardType함수안에 when에 케이스 조건을 지속적으로 추가를 해줘야한다.

잊어버리고 추가를 하지않는 경우, when의 else로 빠지게 되어서, 전혀 다른 동작을 하게된다.

 

이걸 막기위해서, Sealed class등을 사용하기도 하지만, 결국 지속적으로 when에 추가를 해주는것은 똑같긴하다.

(다만, 컴파일단에서  추가안된 경우를 미리 잡아준다는점이 다르긴하다)

 

이런경우, interface를 사용하면 좀더 깔끔하게 구현이 가능하다. (팩토리패턴)

 

interface CardTypeHandler {
    fun supportType():CardType
    fun printCardType()
}

@Component
class CardATypeHandler: CardTypeHandler {
    override fun supportType() = CardType.A
    override fun printCardType() = println("A")
}

@Component
class CardBTypeHandler: CardTypeHandler {
    override fun supportType() = CardType.B
    override fun printCardType() = println("B")
}

@Service
class TestService(cardTypeHandlers:List<CardTypeHandler>) {

    val cardTypeHandlersMap = cardTypeHandlers.associateBy { it.supportType() }

    fun printCardType(cardType:CardType) = cardTypeHandlersMap[cardType]
}

 

조금 코드량이 늘어났지만, 유지보수하는데 훨씬 강력하다. 

CardTypeHandler 인터페이스를 구현하는 구현체를 만들기만하면, 별도로,

when등으로 구현된 케이스에 대한 조건을 추가하지 않아도, 동작하게 만들수있다.

 

이렇게 구현해놓으면,

구현체만 만들면, TestService에서 printCardType에서 추가된 구현체로 동작을 하게 할수있다.

 

깔끔하게 위와같이 만들수있는 것은 스프링에서 빈을 주입받을때, 같은 인터페이스를 구현한 Bean의 List를 주입받을수있기에 가능하다.

 

이와 비슷하게 동작하는 대표적인 예가, 스프링 시큐리티 필터, 인터셉터등일것이다.

해당 필터나 인터셉터들도 위와같이 구현되어, 체이닝을 하고, 우선순위로 동작하기 쉽고, 구현체 추가만을통해, 동작하게 만든 대표적인 예이다.

 

위 패턴은 특히 이벤트리스너를 체이닝하여 동작하게 만들때, 정말 유용하다!!

 

항상 코드가 짧고, 단순하다고 좋은 코드가 아닐수도있다. (물론 대부분은 좋은 코드지 :) )

하지만, 위와같이, 코드가 다소 길어지더라도,

향후에 다른 개발자가 개발을 하더라도 구현체를 구현하고 어딘가에 별도로 케이스를 추가해야 동작하는 코드보다, 

(가장 최악, 코드의 백그라운드를 알아야하는것!!)

위와 같은 코드가  더 좋은 코드가 될수있다는 점은 명심하자!!

 

개발에서 심플이즈베스트 명언이 있지만, 경우에 따라서는 아닌경우도 있다는점.....ㅋ