다형성
다형성(多形性, polymorphism)이란 프로그래밍 언어의 자료형 체계의 성질을 나타내는 것으로, 프로그램 언어의 각 요소들(상수, 변수, 식, 객체, 함수, 메소드 등) 다양한 자료형(type)에 속하는 성질을 말한다.[1] 영어 발음대로 폴리모피즘(polymorphism)이라고 하기도 한다. 다형성으로 인해 하나의 함수는 상속을 통해 기능을 확장시키고 더 변경하는 것이 가능하다. 자바에서 다형성을 부모 클래스 타입의 참조변수로 자식 클래스 타입의 인스턴스를 참조할 수 있도록 하여 구현한다. 다형성은 상속, 추상화와 더불어 객체 지향 프로그래밍을 구성하는 중요한 특징 중 하나이다.[2] 다형성의 반대말은 단형성(monomorphism)으로, 프로그램 언어의 각 요소가 한 가지 형태만 가지는 성질을 가리킨다.
목차
개요
다형성이란 같은 자료형에 여러 가지 객체를 대입하여 다양한 결과를 얻어내는 성질을 의미한다.[3]
구성 요소
서브타입 다형성
서브타입 다형성(Subtype Polymorphism / Inclusion Polymorphism / Subtyping) : 서브타입 다형성은 객체 지향에서 흔히 얘기하는 다형성을 이야기한다. 서브타입 다형성의 핵심은 바로 대치 가능성(Substitutability)라고 하는 것이다. 어떤 타입의 클래스를 요구하는 상황에서 클래스를 가지는 객체뿐만 아니라 그것의 서브타입(subtype)을 가지는 객체도 대신 사용할 수 있다는 것이다.[4] 기초 클래스 또는 어떠한 인터페이스를 구현하는 상위 클래스를 생성하고, 해당 클래스를 상속받는 다수의 하위 클래스들을 만들어 상위 클래스의 포인터나 참조 변수 등이 하위 클래스의 객체를 참조하게 하는 것이다. 이때 각각의 하위 클래스는 상위 클래스의 메소드 위에 자신의 메소드를 덮어쓰는 메소드 오버라이딩(Method overriding)을 수행하며, 상위 클래스의 참조 변수가 어떤 하위 클래스의 객체를 참조하느냐에 따라 호출되는 메소드가 달라진다. Java, C++, C#, Python, Ruby 등의 객체 지향 언어들은 기본적으로 지원하는 개념이다.[5]
매개변수 다형성
매개변수 다형성(Parametric Polymorphism) : 어떤 소프트웨어 시스템을 개발하든지 간에 자료구조는 필수적으로 사용된다. 타입을 매개변수로 받아 새로운 타입을 되돌려주는 기능이다. 타입 매개변수를 정의한 클래스 혹은 메소드는 사용할 때 매개변수에 타입을 지정하게 되며, 컴파일 시 지정한 타입에 따라 해석된다.[5] 스택, 큐, 트리, 테이블 등이 바로 그런 것들인데. 파라메트릭 다향성은 타입이 다르기 때문에 모든 코드를 다시 작성해야 하는 문제를 해결해 주기 위해서 존재한다고 봐도 된다. 그렇기에 실용적으로 매우 편리하다는 장점을 가진다.[4]
템플릿
템플릿(Template) : C++에서 사용하는 개념으로, 타입 매개변수를 입력한 타입으로 치환한 코드를 생성하는 방식이다. 타입뿐 아니라 변수도 입력할 수 있으며, 객체 내부에서 연산이나 함수 호출을 할 수 있지만, 해당 연산이나 함수가 정의되지 않은 타입을 매개변수로 넣으면 컴파일 에러가 발생하며 컴파일이 느려지고 파일이 커진다.
제네릭
제네릭(Generic) : Java와 C# 등에 도입된 개념으로, 지정한 타입 매개변수에 해당하는 타입만을 사용하겠다고 약속하는 방식이다. 타입 매개변수가 특정 객체를 상속할 경우 상속하는 객체의 함수는 호출할 수 있지만 그렇지 않을 경우 타입 매개변수로 지정된 객체의 멤버에는 접근할 수 없다.
임시 다형성
임시 다형성(Ad hoc Polymorphism) : 애드혹 다형성에는 오버로딩, 코어션이 모여있다고 볼 수 있다.[4]
함수 오버로딩
함수 오버로딩(Function overloading) : C++과 C#, Java에서는 함수 오버로딩을 통해 동일한 이름의 함수를 매개변수에 따라 다른 기능으로 동작하도록 할 수 있다. 함수 오버로딩을 너무 많이 사용하면 전체적인 코드의 유지 보수가 어려워지므로, 템플릿 또는 제네릭으로 대체하는 것이 일반적이다.[5]
연산자 오버로딩
연산자 오버로딩(Operator overloading) : C++, C# 등에서는 연산자를 오버로딩해서 기본 연산자가 해당 클래스에 맞는 역할을 수행하게 하는 것이 가능하다. Java에서는 연산자의 오버로딩이 불가능하다. Perl 6나 Smalltalk, F#, Kotlin 등 연산자의 신규 정의가 가능한 언어도 있다.[5]
강제 다형성
강제 다형성(Coercion Polymorphism)
묵시적 형 변환
묵시적 형 변환(Implicit type coercion) : 'double a = 30;'이라는 식이 실행되면 int형 값 30은 double로 묵시적 형 변환이 이루어진다. double은 int보다 크기가 큰 자료형이므로, 이러한 형 변환을 자료형 승급(Type promotion)이라고 한다. C++의 변환 생성자에 의한 형 변환도 묵시적 변환에 속하며, 이를 막으려면 생성자 앞에 explicit 키워드를 추가해야 한다.[5]
명시적 형 변환
명시적 형 변환(Explicit type coercion) : 'double a = (double)30;'이라는 식은 위와 동일한 결과를 내지만, (double)을 통해 int형 값 30이 double형으로 변환됨을 명시적으로 표현하였다.[5]
특징
- 부모 클래스 타입의 참조 변수로 자식 클래스의 인스턴스 참조 가능. 부모 클래스 타입의 참조 변수로 자식 클래스에 있는 멤버들에 접근 가능하다. (부모 클래스에서 상속받은 멤버만 접근 가능. 자식 클래스에서 만들어진 멤버들은 접근 불가.)[6]
- 자식 타입의 참조 변수로 부모 타입의 인스턴스로 참조 불가하다.[6]
- 반드시 상속관계가 이뤄져야 한다.[6]
- 상속과 인터페이스를 통해 이루어진다.
- 인터페이스가 상속보다 다형성에 더욱 유연함을 제공한다.
활용
단형성 체계를 가진 프로그램 언어에서는 함수는 각각 한 가지 의미로 식별되는 이름과 결합되어 있어 다른 동작을 구현하기 위해서는 다른 이름을 써야 한다. 예를 들어 어떤 값을 문자열 형식으로 변환하는 단순한 경우를 생각하며, 단형성 형태 체계를 가지지 못한 언어에서는, 개별 함수일 것이다.[1]
//숫자를 문자열로 바꾸는 경우 string = StringFromNumber(number);
//날짜를 문자열로 바꾸는 경우 string = StringFromDate(date);
한편 다형성 체계를 가진 언어에서는, StringValue와 같은 범용 메소드 이름을 정의하여 형태에 따라 각각 적절한 변환 방식을 정의해둠으로써 객체의 종류와 상관없는 추상도가 높은 변환 형식을 구현할 수 있다.[1]
//숫자를 문자열로 바꾸는 경우 string = number.StringValue();
//날짜를 문자열로 바꾸는 경우 string = date.StringValue();
물론 StringValue 메소드의 정의는 형태별로 따로 수행되어야 하기 때문에 전체적으로 코드의 분량이 감소하는 것은 아니다.(다만 상속에 의한 재사용 가능). 추가로 '올바른 동작'에 관한 것은 객체의 설계에 따라 달라질 수 있으므로, 다형성을 잘 다루려면 체계 전체를 파악하는 뛰어난 설계 능력이 요구된다고 볼 수 있다.[1]
각주
참고 자료
- 강관우, 〈다형성에 대해서 설명해보세요.〉, 《개발자 지망생》, 2016-05-08
- TCP School, 〈다형성의 개념〉, 《코딩의 시작, TCP School》
- JOKER, 〈(JAVA/자바) 다형성(polymorphism)의 개념/의미/예제〉, 《JOKER's ROOM》, 2017-04-10
- 윤연식, 〈다형성〉,《윤연식》, 2008-0-13
- devbox, 〈(Java) 다형성〉, 《장인개발자를 꿈꾸는 :: 기록하는 공간》, 2014-12-21
- 위키백과, 〈다형성(컴퓨터 과학)〉, 《위키백과》, 2020-04-24
- 나무위키,〈객체 지향 프로그래밍〉, 《나무위키》
같이 보기