12장. 다형성
12장. 다형성
상속과 합성은 재사용 대상이 다르다. 상속은 부모 클래스 안에 구현된 코드 자체를 재사용하지만 합성은 포함되는 객체의 퍼블릭 인터페이스를 재사용한다.
❐ 1. 다형성
1-1. 들어가기
다형성이란?
- 여러 타입을 대상으로 동작할 수 있는 코드를 작성할 수 있는 방법
다형성의 분류
- 오버로딩 다형성
- 하나의 클래스 안에 동일한 이름의 메서드가 존재하는 경우
- 강제 다형성
- 서로 다른 타입을 강제 변환해서 동일한 연산을 수행
- 매개변수 다형성
- 제네릭과 관련된 다형성 → 타입을 나중에 구체화하여 재사용
- 포함 다형성
- 객체지향에서 가장 대표적인 다형성
- 메시지는 같지만, 수신 객체의 실제 타입에 따라 실행 메서드가 달라짐
- 일반적으로 말하는 “다형성”은 이걸 의미
- 보통 상속 + 메서드 오버라이딩으로 구현
❐ 2. 상속의 양면성
2-1. 들어가기
상속이 무엇이며 언제 사용해야 하는지에 대한 이해가 중요
- 객체지향의 기본 아이디어는 데이터와 행동을 객체라는 하나의 실행 단위로 통합하는 것
- 상속은 부모 클래스의 데이터와 메서드(행동)를 자식 클래스가 자동으로 포함·공유하는 메커니즘이다.
- 하지만 상속을 코드 재사용 수단으로만 보는 것은 오해다.
- 상속의 진짜 목적은 다형성을 가능하게 하는 타입 계층(type hierarchy)을 구성하는 데 있다.
- 타입 계층에 대한 고민 없이 상속을 남용하면 이해하기 어렵고 유지보수하기 힘든 코드가 된다.
❐ 3. 업캐스팅과 동적바인딩
3-1. 같은 메시지, 다른 메서드
한 줄 요약
- 같은 메시지(evaluate)를 보내도, 실제 실행되는 메서드는 객체의 실제 타입에 따라 달라진다.
핵심 포인트
- Professor는 Lecture 타입에만 의존
- Professor는 lecture.evaluate()를 호출할 뿐
- 실제로는 Professor 코드는 전혀 바뀌지 않음
- Lecture 인스턴스 → Lecture.evaluate()
- GradeLecture 인스턴스 → GradeLecture.evaluate()
왜 중요한가?
- 클라이언트(Professor)는 구현을 몰라도 된다.
- 새로운 Lecture 하위 클래스가 추가돼도 → Professor는 수정 없음
메시지 중심 사고
- “어떤 메서드를 호출할까?” ❌
- “누구에게 어떤 메시지를 보낼까?” ✅
3-2. 업캐스팅
한 줄 요약
1
Lecture lecture = new GradeLecture(...); // 업캐스팅
- 부모 타입 변수에 자식 객체를 넣는 것 → 객체지향 다형성의 출발점
특징
- 명시적 캐스팅 불필요
- 컴파일 타임에 안전
- 부모가 이해하는 메시지만 보낼 수 있음.
3-3. 동적 바인딩
한 줄 요약
- 실행할 메서드는 컴파일 시점이 아니라, 실행 시점에 “객체의 실제 타입”으로 결정된다.
정적 바인딩 vs 동적 바인딩
| 구분 | 정적 바인딩 | 동적 바인딩 |
|---|---|---|
| 결정 시점 | 컴파일 타임 | 런타임 |
| 기준 | 변수 타입 | 객체 실제 타입 |
| 대표 | 함수 호출 | 객체 메시지 전송 |
3-4. 세 개념의 연결 관계
한 문장으로 정리
- 업캐스팅 덕분에 부모 타입으로 협력할 수 있고
- 동적 바인딩 덕분에 실행 시점에 올바른 메서드가 선택되며,
- 그 결과 같은 메시지가 다른 행동으로 이어진다.
❐ 4. 동적 메서드 탐색과 다형성
4-1. 들어가기
self를 기준으로 한 “동적 메서드 탐색” 흐름
- 메시지를 받으면
- self가 가리키는 실제 객체의 메모리 위치로 이동
- 그 객체가 가리키는 class 포인터를 따라 클래스 정보 확인
- 그 클래스에 정의된 메서드 목록에서 탐색 시작
- 없으면 parent를 따라 상위 클래스로 이동
동적 메서드 탐색의 핵심 원리 2가지
- 자동적인 메시지 위임
- 자식 클래스가 메시지를 이해하지 못하면
- 프로그래머 개입 없이
- 자동으로 부모 클래스에게 처리를 위임한다.
- 실행 시점에 결정되는 동적 문맥
- 어떤 메서드를 실행할지는 컴파일 시점이 아닌 실행 시점(runtime)에 결정
- 기준은 오직 하나 : self를 가리키는 실제 객체
4-2. 자동적인 메시지 위임
자동적인 메시지 위임 (핵심 개념)
- 객체가 이해하지 못하는 메시지는, 상속 계층을 따라 부모 클래스로 자동 위임된다.
- 메시지를 받은 객체가 해당 메서드를 알고 있으면 실행, 모르면 부모 클래스로 위임
- 이 과정을 적절한 메서드를 찾을 때까지 반복. 즉,
상속 계층 == 메서드 탐색 경로
동적 메서드 탐색 (Dynamic Method Lookup)
1
2
Lecture lecture = new GradeLecture();
lecture.evaluate();
- 메서드 탐색은 변수의 타입이 아니라, 실제 객체의 클래스에서 시작한다
- 탐색 순서
- GradeLecture 클래스에서 evaluate() 찾기
- 없으면 → 부모 Lecture
- 없으면 → Object
- 끝까지 없으면 → RuntimeException
이 챕터의 진짜 메시지 (중요 ⭐️)
- 메서드는 “호출”되는 게 아니라 탐색된다
- 상속은 코드 재사용이 아니라 메시지 위임 구조
- 다형성은
if/else가 아니라 탐색 경로를 맡기는 것.
4-3. 동적인 문맥
self가 문맥을 만든다
- self → 메시지를 “받은 객체”
- 메서드 탐색은 항상 self의 클래스에서 시작
중요한 전환점: self 전송 (self send)
1
2
3
4
5
6
7
public String stats() {
return String.format(
"Title: %s, Evaluation Method: %s",
title,
getEvaluationMethod()
);
}
- 많은 사람들은 “Lecture 클래스의 getEvaluationMethod를 호출한다”라고 해석한다.
- 하지만 실제 의미는 “self에게 getEvaluationMethod 메시지를 다시 보낸다.”이다.
self 전송이 일어나면 탐색이 다시 시작된다.
- 최악의 경우에는 실제로 실행될 메서드를 이해하기 위해 상속 계층 전체를 훑어가며 코드를 이해해야 한다.
4-4. 이해할 수 없는 메시지
이해할 수 없는 메시지란?
- 객체가 메시지를 받았는데 자기 자신도, 부모 클래스들도 그 메시지를 처리할 메서드를 가지고 있지 않은 경우
정적 타입 언어 vs 동적 타입 언어
- 정적
- 컴파일 시점에 메시지 처리 가능 여부를 판단
- 상속 계층 전체를 컴파일러가 미리 검사
- 동적
- 컴파일 단계 자체가 없음
- 메시지 처리 가능 여부는 실행 시점에만 알 수 있음
self vs super 한 방에 비교
| 구분 | self 전송 | super 전송 |
|---|---|---|
| 탐색 시작 위치 | 실행 시점에 동적으로 결정 | 컴파일 시점에 부모 클래스로 고정 |
| 다형성 | 매우 강함 | 제한적 |
| 목적 | 행동의 확장/변경 | 코드 재사용 |
| 안정성 | 낮음 | 높음 |
❐ 5. 상속 대 위임
5-1. 위임과 self 참조
“위임”이란 정확히 뭐고, “포워딩”과 뭐가 다른가?
- 위임(Delegation)
- 다른 객체에게 일을 시키지만, 실행 문맥(self/this)을 유지시키는 갓
- 포워딩(Forwarding)
- 다른 객체에게 그냥 넘김
- 실행 문맥(self/this) 까지 유지하지 않음.
5-2. 프로토타입 기반의 객체지향 언어
자바스크립트에서 상속
- “클래스 간 관계”가 아니라 “객체 사이의 메시지 위임(prototype chain)”으로 구현된다.
prototype 이란?
- 모든 자바스크립트 객체는 내부적으로
Prototype 링크를 가진다 - 코드에서는 이것을 prototype으로 다룬다
- 이 링크는 “부모 객체”를 가리킨다
클래스 기반 상속과의 정확한 대응
| 클래스 기반 언어 | 프로토타입 기반 JS |
|---|---|
| 클래스 간 상속 | 객체 간 위임 |
| 메서드 오버라이딩 | prototype 체인 상 메서드 재정의 |
| this = 현재 인스턴스 | this = 메시지 수신 객체 |
| 정적 구조 | 동적 연결 |
이 기사는 저작권자의
CC BY 4.0
라이센스를 따릅니다.

