12 다형성

 

들어가며

 

상속의 목적은 코드 재사용이 아니다. 상속은 타입 계층을 구조화하기 위해 사용해야 한다.

 

 

 

01 다형성

 

다형성이란? 하나의 추상 인터페이스에 대해 코드를 작성하고 이 추상 인터페이스에 대해 서로 다른 구현을 연결할 수 있는 능력을 말한다. 즉 여러 타입을 대상으로 동작할 수 있는 코드를 작성할 수 있는 방법이라고 할 수 있다.

 

 

 

02 상속의 양면성

 

데이터 관점의 상속

 

 

데이터 관점의 상속은 자식 클래스의 인스턴스 안에 부모 클래스의 인스턴스를 포함하는 것으로 볼 수 있다.

 


행동 관점의 상속

 

행동 관점의 상속은 부모 클래스가 정의한 일부 메서드를 자식 클래스의 메서드로 포함시키는 것을 의미한다.

즉, 부모 클래스의 모든 퍼블릭 메서드가 자식클래스의 퍼블릭 인터페이스에 포함된다.

 

 

 

03 업캐스팅과 동적 바인딩

 

업캐스팅

 

상속, 업캐스팅을 통해 부모 클래스의 인스턴스에게 전송할 수 있는 메시지를 자식 클래스의 인스턴스에게 전송할 수 있다.

즉, 새로운 타입이 추가될 때마다 자식클래스를 추가하여 변경아닌 확장에 초점을 맞춰 다형적인 코드를 작성할 수 있다.

 


동적 바인딩

 

객체지향 언어에서는 메세지를 수신했을 때 실행될 메서드가 런타임에 결정된다. 이를 동적바인딩 또는 지연 바인딩이라고 부른다.

예를 들면 어떠한 메시지 전송시 구체 클래스가 아닌 추상 클래스에 의존하여 추상 클래스에 정의된 퍼블릭 인터페이스에 메세지를 전송하게 되면 해당 추상클래스를 구현하고있는 여러 구체 클래스 중에서 메세지를 처리하고자 업캐스팅된 구체클래스의 메서드가 처리한다. 이는 런타임에 결정된다.

 

 

 

04 동적 메서드 탐색과 다형성

 

객체지향에서는 메세지 전송시 다음과 같은 규칙에 따라 전송받은 메세지를 처리할 메서드를 선택한다.

 

  • 메세지를 수신한 객체는 먼저 자신을 생성한 클래스에 적합한 메서드가 존재하는지 검사한다. 존재하면 메서드를 실행하고 탐색을 종료한다.
  • 메서드를 찾지 못했다면 부모 클래스에서 메서드 탐색을 계속한다. 이 과정은 적합한 메서드를 찾을 때까지 상속 계층을 따라 올라가며 계속된다.
  • 상속 계층의 가장 최상위 클래스에 이르렀지만 메서드를 발견하지 못한 경우 예외를 발생시키며 탐색을 중단한다.

메세지 탐색시 중요한 요소로 self 참조 변수가 있다.

객체가 메세지를 수신하면 컴파일러는 self 참조라는 임시 변수를 자동으로 생성한 뒤 self 참조 변수가 메시지를 수신한 객체를 가리키도록 설정한다. 동적 메서드 탐색은 self가 가리키는 객체의 클래스에서 시작해서 상속 계층의 역방향(자식클래스 -> 부모클래스)으로 이뤄지며 메서드 탐색이 종료되는 순간 self 참조는 자동으로 소멸된다.

 

 

 

 

곡선이 있는 박스는 GradeLecture 인스턴스의 메모리 상태를 나타내며 이 객체는 getScores 메세지를 전송받아 해당 메세지를 처리할 메서드를 탐색하게 된다. 객체는 인스턴화되어 메모리에 로드된 GradeLecture 클래스 정보를 가리키는 class 포인터를 가지며 class 포인터를 통해 GradeLecture 클래스 정보를 읽어 메서드를 탐색한다.

그림을 보면 getScores() 메서드는 GradeLecture클래스에 구현되어있지 않기 때문에 parent 포인터를 따라 부모클래스인 Lecture 클래스로 이동하여 탐색 계속 수행한다.

Lecture 클래스에는 getScores() 메서드가 존재하기 때문에 여기서 탐색을 종료하지만 만약 없을시 Object까지 탐색을 수행한다. 만약 Object에서도 찾지 못했다면 에러를 발생시키고 탐색을 종료한다.

 

즉 동적 메서드 탐색은 두가지 원리로 구성된다.

 

첫 번째 원리는 자동적인 메세지 위임이다.

자식 클래스는 자신이 이해할 수 없는 메세지를 전송받은 경우 상속 계층을 따라 부모클래스에게 처리를 위임한다.

 

두 번째 원리는 메서드 탐색을 위해 동적인 문맥을 사용한다는 것이다.

메세지를 수신했을 때 실제로 어떤 메서드를 실행할지 컴파일 시점이 아닌 실행 시점에 이뤄지며 메서드를 탐색하는 경로는 self 참조를 이용해서 결정한다.

 


자동적인 메세지 위임

 

그림에서 evaluate 메세지 수신 했다면 GradeLecture에서 탐색을 종료할 것이다. 이는 흔히 알고 있던 메서드 오버라이딩이다.

즉, 동일한 시그니처를 가지는 자식 클래스의 메서드는 부모 클래스의 메서드를 감춘다.

 

반면, 시그니처가 완전히 동일하지 않고 이름만 같다면 이것이 바로 메서드 오버로딩이다. 이러한 메서드들은 상속 계층에 걸쳐 사이좋게 공존할 수도 있다.

 

대부분의 사람들은 하나의 클래스 안에서 같은 이름을 가진 메서드를 정의하는 것만이 메서드 오버로딩으로 생각하지만 상속 계층 사이에서 같은 이름을 가진 메서드를 정의하여 메서드 오버로딩이 구현될 수 도 있다. (C++ 제외)

 


self 대 supre

 

자식 클래스에서 부모 클래스의 구현을 재사용해야하는 경우가 있다. 자식클래스에서 부모 클래스의 인스턴스 변수나 메서드에 접근하기 위해 supre라는 참조 내부 변수를 제공한다.

 

그러나 사실 supre 참조의 용도는 부모 클래스에 정의된 메서드를 실행하기 위한 것이 아니다. super 참조의 정확한 의도는 '지금 이 클래스의 부모 클래스에서부터 메서드 탐색을 시작하세요'다. 만약 부모클래스에서 찾지 못하다면 상위 부모 클래스까지 올라가면서 탐색을 시도한다. 

 

정리하면 self 전송이 메세지를 수신하는 객체의 클래스에 따라 메서드를 탐색할 시작 위치를 동적으로 결정하는 데 비해 super 전송은 항상 메세지를 전송하는 클래스의 부모 클래스에서 부터 시작된다.

 

 

 

 

 

'📚 Book > Object' 카테고리의 다른 글

14 일관성 있는 협력  (0) 2020.05.02
13 서브클래싱과 서브타이핑  (0) 2020.04.26
11 합성과 유연한 설계  (0) 2020.03.29
10 상속과 코드 재사용  (0) 2020.03.22
09 유연한 설계  (1) 2020.03.14