함수의 다형성, 하위 호환성
객체지향 관점에서, 기존에 정의한 클래스를 사용하는 함수를 안전하게 대체 또는 선택이 가능하게 하려면 어떤 점들을 고려해야 하는지 확인해 보겠습니다.
첫번째로, 가장 간단한 방법은 완전히 동일한 프로토타입의 함수 구현을 새로 만들어 필요한 곳에 대입하는 것입니다. 이 경우 함수의 프로토타입만 동일하다면 특별히 고려해야할 점이 없습니다.
두번째는, 객체지향의 다형성을 함수에 적용하는 방법입니다. 조금 복잡하지만 재활용성이 높은 방법입니다. 함수의 매개변수가 클래스일 경우, 새로 교체할 함수의 매개변수가 기존 함수의 매개변수와 같은 상속 계층에 있는 경우 이 방법을 고려해 볼 수 있습니다.
첫번째 방법은 별로 생각할 것이 없습니다. 더 이상 추가적인 설명도 필요 없을 것입니다.
하지만 두번째 방법은 상속 계층에 대해 생각해 봐야 합니다. 함수를 대체하기 위해 매개변수와 리턴값의 상속계층에 대해 검토해봐야 합니다.
결론부터 이야기 하면, 상속계층을 통해 객체지향의 다형성을 함수에 적용하면, 같은 상속계층에 있는 서로 다른 타입의 객체를 사용하는 함수를 기존 함수 구현에 안전하게 대체할 수 있습니다.
이제부터 이러한 결론을 확인해 보기 위해 간단하게 상황설정을 하고, 이런 상황에 어떤 문제점들이 발생하는지, 또 어떻게 해결이 되어야 하는지를 확인해 보겠습니다.
상황 설정
사전 정보
예제 코드는 코틀린 코드로 작성했습니다.
표기법은 subtyping 에서 사용하는 것을 일부 사용합니다. ( "<:" )
A가 B를 상속 받는다면 A <: B 로 표기합니다.
상속 계층 구조
우선 Animal, Bird, Duck / Eagle 의 상속 관계가 다음과 같다고 가정합니다. 상속 관계를 상식적으로 정의 했으므로 특별히 문제가 될 만한 부분은 없을 것입니다..
Duck <: Bird <: Animal
Eagle <: Bird <: Animal
기존에 사용하던 함수
move( bird: Bird ): Bird
사용 환경
예제
완전하게 돌아가는 전체 소스는 이후에 나올 결론 챕터의 예제를 참조하면 됩니다.
기존에 사용하던 함수 move( bird: Bird ): Bird 는 Bird 타입의 객체를 리턴하고 있으며 동일한 타입인 Bird나 상위 타입(supertype)인 Animal 타입의 변수에 리턴값을 전달하고 있습니다. 즉 subtype객체를 supertype 변수에 대입하는 경우로만 구현되어 있어 컴파일 에러가 발생하지 않는 정상적인 상황으로만 사용하고 있다고 가정합니다.
문제 제기
기존에 사용하던 함수 move( bird: Bird ): Bird 를 다음의 함수 들로 대체할 수 있나?
move( bird: Bird ): Bird 함수에서 Bird 클래스를 사용하는 부분(매개변수 타입, 리턴 타입)을 같은 상속계층에 있는 다른 클래스로 교체 가능한지 각 타입 별로 확인해 보겠습니다.
move( duck: Duck ): Duck
매개변수 타입 다형성 확인
기존에 사용하던 함수의 정의 move( bird: Bird ): Bird 에 의해 새로 교체될 함수 move( duck: Duck ): Duck 에는 duck 매개변수에 Duck, Eagle, Bird Type의 객체가 입력될 수 있습니다. 그런데 새로 교체될 함수에서는 Bird type의 객체가 입력될 경우 supertype 객체를 subtype 매개변수에 대입하게 되므로 안전하지 않습니다. Eagle일 경우도 같은 레벨의 subtype끼리 대입을 하게 되므로 안전하지 않습니다.
리턴 타입 다형성 확인
기존 함수의 리턴 타입인 Bird를 새로 교체할 함수에서 Duck 타입으로 교체하는 경우 새로운 함수에서 리턴하게 될 Duck 타입의 객체는, 기존에 사용하던 함수의 리턴 값(Bird)을 전달 받고 있던 Bird나 Animal 타입의 변수에 대입 가능(subtype 객체를 supertype 변수에 대입)하므로 안전합니다.
move( animal: Animal ): Animal
매개변수 타입 다형성 확인
기존에 사용하던 함수의 정의 move( bird: Bird ): Bird 에 의해 새로 교체될 함수 move( animal: Animal ): Animal 에는 animal 매개변수에 Duck, Eagle, Bird Type의 객체가 입력될 수 있습니다. 즉 supertype 매개변수에 subtype 객체가 대입되므로 안전합니다.
리턴 타입 다형성 확인
기존 함수의 리턴 타입인 Bird를 새로 교체할 함수에서 Animal 타입으로 교체하는 경우 상위 타입의 객체, 즉 Animal 타입의 객체를 Bird 타입의 변수에 전달하는 경우가 발생할 수 있습니다. 이는 supertype 객체를 subtype 변수에 대입하는 것이므로 안전하지 않습니다.
move( animal: Animal ): Eagle
매개변수 타입 다형성 확인
기존에 사용하던 함수의 정의 move( bird: Bird ): Bird 에 의해 새로 교체될 함수 move( animal: Animal ): Eagle 에는 animal 매개변수에 Duck, Eagle, Bird Type의 객체가 입력될 수 있습니다. 즉 supertype 매개변수에 subtype 객체가 대입되므로 안전합니다.
리턴 타입 다형성 확인
기존 함수의 리턴 타입인 Bird를 새로 교체할 함수에서 Eagle 타입으로 교체할 경우, 새로운 함수에서 리턴할 Eagle 타입의 객체를 Bird, Animal 타입의 변수에 대입하게 됩니다. 즉 subype 객체를 supertype 변수에 대입하므로 안전합니다.
결론
안전하게 대체 가능한 함수
현재까지의 예제들을 확인해 보면 안전하게 대체할 수 있는 함수의 형식은 다음과 같습니다.
move( animal: Animal ): Eagle
move( animal: Animal ): Duck
다음은 Kotlin에서 바로 확인해 볼 수 있는 예제 소스입니다. 이미 Kotlin과 객체지향 개념을 잘 활용하는 경우라면 아래 소스 코드만 테스트 해봐도 전체 내용을 이해할 수 있을 것입니다..
Kotlin 예제
요약
함수 매개변수의 타입은 대체할 함수의 매개변수 타입보다 상위 타입(supertype) 이어야 합니다.
함수 리턴 타입은 대체할 함수의 리턴 타입 보다 하위 타입(subtype) 이어야 합니다.
최종 정리
기존의 함수를 다형적으로 안전하게 대체 하려면 새로운 함수의 매개변수의 타입은 일반화 되고, 리턴 값의 타입은 구체화 되어야 합니다.
'코틀린( Kotlin )' 카테고리의 다른 글
코틀린 7-3 Variance - 코틀린에서의 불변, 공변, 반공변 개념 및 문법 (0) | 2020.04.25 |
---|---|
코틀린 7-2 Variance - 불변 공변 반공변에 대한 일반적인 개념 (0) | 2020.04.24 |
코틀린 7-1 Generics (0) | 2020.04.23 |
코틀린 6-10 Annotations - 표준 애노테이션들 (0) | 2020.04.22 |
코틀린 6-9 Annotations - Annotation instance 접근 (0) | 2020.04.21 |