1. 개요
•
상속을 사용하는 이유는 단순히 코드의 재사용을 위해서가 아닌 동일 행동 그룹을 위해서도 이용된다.
•
객체지향 패러다임이 주목받기 시작하던 초기엔 상속은 타입 계층과 다형성을 구성할 수 있는 거의 유일한 방법 이었음
◦
많은 시간이 지난 지금도 여전히 상속은 다형성을 구현할 수 있는 가장 일반적인 방법이다.
◦
But. 최근 언어들은 상속 이외에도 다형성을 구현할 수 있다.
▪
과거의 비해 상속의 중요성이 줄어들었기 때문
2. 다형성
•
다형성 = 하나의 추상 인터페이스에 대해 여러 코드를 작성하고 이 추상 인터페이스에 대해 서로 다른 구현을 연결할 수 있는 능력
•
오버 로딩 다형성
◦
일반적으로 하나의 클래스 안에 동일한 이름의 메서드가 존재하는 경우를 카리 킨다.
•
강제 다형성
◦
언어가 지원하는 자동적인 타입 변환이나 사용자가 직접 구현한 타입 변환을 이용해 동일한 연산자를 다양한 타입에 사용할 수 있는 방식을 가리킨다.
•
오버 로딩 다형성
◦
일반적으로 하나의 클래스 안에 동일한 이름의 메서드가 존재하는 경우를 가리킨다.
◦
유사한 역할을 하는 메서드지만 시그니처가 다른 경우 사용한다.
•
매개변수 다형성
◦
제네릭 프로그래밍과 관련이 깊다. 변수나 메서드의 매개변수 타입을 임의의 타입으로 선언한 후 사용하는 시점에 구체적인 타입으로 지정하는 방식
•
포함 다형성
◦
메시지가 동일하더라도 수신한 객체의 타입에 따라 실제로 수행되는 행동이 달라지는 능력을 의미한다. 이는 서브타입 다형성이라고도 한다.
3. 상속의 양면성
•
객체지향 패러다임의 근간을 이루는 아이디어는 데이터와 행동을 객체라고 불리는 하나의 실행 단위 안으로 통합하는 것이다.
•
객체지향 프로그램을 작성하기 위해서는 항상 데이터와 행동이라는 두 가지 관점을 함께 고려해야 한다.
해당 장에서는 다음과 같은 핵심 개념들을 소개하고 있었다.
•
업 캐스팅
•
동적 메서드 탐색
•
동적 바인딩
•
self 참조
•
super 참조
4. 업캐스팅과 동적 바인딩
•
업캐스팅 = 부모 클래스 타입으로 선언된 변수에 자식 클래스의 인스턴스를 할당하는 것이 가능하다.
•
동적 바인딩 = 선언된 변수의 타입이 아니라 메시지를 수신하는 객체의 타입에 따라 실행되는 메서드가 결정된다. 이것은 객체지향 시스템이 메시지를 처리할 적절한 메서드를 컴파일 시점이 아니라 실행 시점에 결정하기 때문에 가능하다.
4.1. 업캐스팅
•
상속을 이용하면 부모의 퍼블릭 인터페이스가 자식의 인터페이스에 합쳐지기 때문에 부모의 인스턴스에게 전송할 수 있는 메시지를 자식의 인스턴스에게도 전송할 수 있다.
◦
또한 컴파일러는 명시적 타입 변환 없이도 자식 클래스가 부모 클래스를 대체할 수 있게 허용한다.
•
이런 특성을 활용하는 대표적인 두 가지가 대입문과 메서드의 파라미터 타입이다.
•
반대로 부모 클래스의 인스턴스를 자식 클래스로 변환하기 위해는 명시적인 타입 캐스팅이 필요하다.
◦
이를 다운 캐스팅이라고 한다.
4.2. 동적 바인딩
•
전통적인 언어에서 함수를 실행하는 방법 = 함수를 호출하는 것
•
객체지향에서 메서드를 실행하는 방법 = 메시지를 전송하는 것
•
전통적인 방법으로는 컴파일 타임에 호출이 결정되는데 비해 객체지향은 호출 시점에 결정된다.
•
정적 바인딩 = 컴파일타임에 호출할 함수를 결정하는 방식
•
동적 바인딩 = 런타임에 실행될 메서드를 결정하는 방식
5. 동적 메서드 탐색과 다형성
•
객체지향은 다음 규칙에 따라 실행 메서드를 선택한다.
◦
메시지를 수신한 객체는 먼저 자신을 생성한 클래스에 적합한 메서드가 존재하는 검사한다. 존재하면 메서드를 실행하고 탐색을 종료한다.
◦
메서드를 찾지 못했다면 부모 클래스에서 메서드 탐색을 계속한다. 이 과정은 적합한 메서드를 찾을 때까지 상속 계층을 따라 올라가며 계속된다.
◦
상속 계층의 가장 최상위 클래스에 이르렀지만 메서드를 발견하지 못한 경우 예외를 발생시키며 탐색을 중단한다.
•
동적 메서드는 self가 가리키는 객체의 클래스에서 시작해 메서드 탐색이 종료되면 self 참조는 자동으로 소멸된다.
5.1. 자동적인 메시지 위임
•
동적 메서드 탐색 입장에서 상속 계층은 메시지 수신 객체가 이해할 수 없는 메시지를 부모 클래스에게 전달하기 위한 물리적 경로를 정의한 것으로 볼 수 있다.
◦
상속 계층 안의 클래스는 메시지를 처리할 방법을 알지 못할 경우 메시지에 대한 처리를 부모 클래스에게 위임한다.
•
상속을 이용할 경우 위임과 관련된 코드를 명시적으로 작성하지 않아도 부모 클래스에게 자동으로 위임된다.
◦
자동적 메시지 위임을 지원하는 방법은 언어에 따라 다를 수 있다.
5.2. 동적인 문맥
•
메시지를 수신한 객체가 무엇이냐에 따라 메서드 탐색을 위한 문맥이 동적으로 바뀐다.
•
동적 문맥을 결정하는 것은 바로 메시지를 수신한 객체를 가리키는 self 참조다.
•
동일한 코드라도 self 참조가 가르키는 객체가 무엇인지에 따라 메서드 탐색을 위한 상속 계층의 범위가 동적으로 변한다.
•
동적으로 문맥이 바뀌면 어떤 메서드가 실행될 지 예측이 어려워지는 경우가 있는데 대표적으로 자신에게 메시지를 다시 전송하는 self 전송이다.
5.3. 이해할 수 없는 메시지
5.3.1. 정적 타입 언어와 이해할 수 없는 메시지
•
정적 타입 언어에서는 코드를 컴파일할 때 상속 계층 안의 클래스들이 메시지를 이해할 수 있는지 여부를 판단한다.
•
때문에 상속 계층 전체를 탐색 후에도 메시지를 처리할 수 있는 메서드를 발견 못할 시 컴파일 에러가 발생한다.
5.3.2. 동적 타입 언어와 이해할 수 없는 메시지
•
동적 타입 언어 역시 메시지를 수신한 객체의 클래스부터 부모 클래스의 방향으로 메서드를 탐색한다.
•
차이점이라면 동적 타입 언어에는 컴파일 단계가 존재하지 않기 때문에 실제로 코드를 실행 해보기 전에는 메시지 처리 가능 여부를 판단할 수 없다는 점이다.
•
동적 타입 언어는 이해할 수 없는 메시지를 처리할 수 있는 능력을 가짐으로써 메시지가 선언된 인터페이스와 메서드가 정의된 구현을 분리할 수 있다.
•
하지만 동적 타입 언어의 이러한 동적인 특성과 유연성은 코드를 이해하고 수정하기 어렵게 만든다.
•
정적 타입 언어에는 이런 유연성은 부족하지만 컴파일타임에 확인되고 이해할 수 없는 메시지는 컴파일에러로 이어져 좀 더 안정적이다.
5.4. self대 super
•
self 참조의 가장 큰 특징은 동적이라는 점이다.
◦
메시지를 수신한 객체의 클래스에 따라 메서드 탐색을 위한 문맥을 실행 시점에 결정함
◦
이런 특성과 대비해 언급할만한 가치가 있는것이 super 참조이다
•
자식 클래스에서 부모 클래스의 구현을 재사용 하는 경우 = super 참조
•
super 참조의 정확한 의도는 지금 self 참조가 가리키는 클래스의 부모 클래스에서 부터 메서드 탐색을 시작하세요다.
•
만약 부모 클래스에서 원하는 메서드를 찾지 못한다면 더 상위의 부모 클래스로 이동하면서 메서드가 존재하는지 검사한다.
•
이것은 super 참조를 통해 실행하고자 하는 메서드가 반드시 부모 클래스에 위치하지 않아도 되는 유연성을 제공한다.
•
이 처럼 super 참조를 통해 메시지를 전송하는 것을 super 전송(super send)이라고 부른다.