본문 바로가기
📚 Kotlin

[Effective Kotlin] 아이템 23. 타입 파라미터의 섀도잉을 피하라

by GroovyArea 2023. 1. 25.

섀도잉이란 무엇인가?

class Person(val name : String) {

	fun addPerson(name : String) {
    	// ...
    }
}

addPerson() 함수의 파라미터가 Person class의 프로터티 이름과 같아진다. 

외부 스코프의 프로퍼티를 가리게 되는 경우를 섀도잉이라 한다.

이를 개발자가 알아차리기 힘든 경우가 생긴다.

 

타입 파라미터에서도 문제가 생긴다.

interface Payment

class KakaoPay: Payment
class NaverPay: Payment

class PaymentHistory<T: Payment> {

	fun <T: Payment> addPayment(payment: T) {
    	// ...
    }
}

 이러한 경우는 PaymentHistory의 제네릭 타입과 addPayment()의 타입 파라미터가 독립적으로 적용된다.

 

val paymentHistory = PaymentHistory<KakaoPay>()

paymentHistory.addPayment(KakaoPay())
paymentHistory.addPayment(NaverPay()) // 말이 안 되면서도 말이 되는 경우..

정상적으로 동작한다. 

문제가 되지 않기에, 개발자는 문제점을 쉽사리 알아차리기 쉽지 않다.

 

문제를 해결해보자

interface Payment

class KakaoPay: Payment
class NaverPay: Payment

class PaymentHistory<T: Payment> {

	fun addPayment(payment: T) {
    	// ...
    }
}

 

PaymentHistory Class의 제네릭 타입을 그대로 addPayment() 함수 파라미터에도 적용시켰다.

 

val paymentHistory = PaymentHistory<KakaoPay>()

paymentHistory.addPayment(KakaoPay())
paymentHistory.addPayment(NaverPay()) // 컴파일 에러 발생! Type mismatch

KakaoPay 타입만 받을 수 있게 클래스 제네릭을 그대로 함수 파라미터로 적용시켰기에, 오직 KakaoPay 타입만을 적용할 수 있다. 

같은 인터페이스를 상속받았더라도 NaverPay 타입은 추가할 수가 없다.

 

클래스에 정의한 제네릭 타입과 별개로 사용하고 싶다면

interface Payment

class KakaoPay: Payment
class NaverPay: Payment

class PaymentHistory<T: Payment> {

	fun <C: T> addPayment(payment: C) {
    	// ...
    }
}

이런 식으로 의도적으로 함수에 제네릭을 명시하여 다른 타입도 적용할 수 있다.

근데 이럴 경우가 많이 있을지는 모르겠다.

반응형