본문 바로가기

IT/오브젝트

ch 12. 다형성

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

객체 지향 시스템은 다음 규칙에 따라 실행할 메서드를 선택한다.

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

 

self 참조

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

 

self 참조에서 상속 계층을 따라 이뤄지는 동적 메세드 탐색

메세드 탐색은 자식 클래스에서 부모클래스 방향으로 진행된다. 즉 자식 클래스에 선언된 메서드가 부모클래스의 메서드보다 더 높은 우선순위를 가지게 된다.

 

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

동적인 문맥 = 메세지를 수신했을 때 실제로 어떤 메서드를 실행할지를 결정하는 것은 컴파일 시점이 아닌 런타임 시점에 이뤄지며, 탐색하는 경로는 self 참조를 이용해서 결정된다.


메서드 오버라이딩

Lecture lectur = new Lecture(...);
lecture.evaluate();

Lecture의 인스턴스를 가리키는 self 참조

self 참조가 가리키는 객체의 클래스인 Lecture에서 시작하게 된다.

Lecture 클래스 안에 evaluate 메서드가 존재하기 때문에 시스템은 메세드를 실행한 후 메서드 탐색을 종료한다.

 

 

Lecture lectur = new GradeLecture(...); // Lecture에 자식클래스
lecture.evaluate();

GradeLecture 클래스 안에서 종료되는 evaluate()에 대한 동적 메서드 탐색

자식 클래스에 evalaute가 정의되어 있기 때문에 GradeLecture에서 종료되며, 자식클래스의 메서드가 부모 클래스의 메서드를 감추게 된다.

 

 

Lecture lectur = new GradeLecture(...); // Lecture에 자식클래스
lecture.average();

GradeLecture에서 시작하여 Lecture에서 끝남.

GradeLecture의 average(gradeNmae)와 Lecture에 average()처럼 이름은 같지만 시그니처가 다른 경우를 메서드 오버로딩이라고 부른다. 

C++에서는 상속계층 사이에서의 메서드 오버로딩은 금지된다고 한다.

 


동적인 문맥

이제 우리는 lecture.evaluate라는 메시지 전송 코드만으로는 어떤 클래스의 어떤 메서드가 실행될지를 알 수 없다는 사실을 이해했다. 중요한것은 수신한 객체가 메서드 탐색을 위한 문맥이 동적으로 바뀐다는 것이다. 그리고 이 동적인 문맥을 결정하는 것은 바로 메시지를 수신한 객체를 가리키는 self 참조다.

 

Self 전송

public class Lecture {

	public String stats() {
		return String.format("Title: %s, Evaluation Method: %s", title, getEvaluationMethod()); }
	}
    
	public String getEvaluationMethod() {
		return "Pass or Fail";
	}
}

자신의 getEvaluationMethod 메서드를 호출한다고 표현하는것은 정확하지 않다.

getEvaluationMethod()라는 구문은 현재 객체에게 getEvaluationMethod 메세지를 전송하는 것이다. 

현재 객체란 self 참조가 가리키는 객체다. 이 객체는 처음에 stats 메시지를 수신했던 바로 그 객체다. 이처럼 self 참조가 가리키는 자기 자신에게 전송하는 것을 self 전송이라고 부른다.

 

Lecture의 stats를 호출 그 안에 getEvaluationMethod() self 전송하는 메서드 탐색 순서

 

public class Grade Lecture extends Lecture { 

	@Override
	public String getEvaluationMethod() {
		return "Grade",
	}
}

self 전송은 self 참조부터 탐색을 다시 시작하게 만든다.

self 전송은 자식 클래스에서 부모 클래스 방향으로 진행되는 동적 메서드 탐색 경로를 다시 self 참조가 가르키는 원래의 자식 클래스로 이동시킨다. 이로 인해 최악의 경우에는 실제로 실행될 메서드를 이해하기 위해 상속 계층 전체를 훑어가며 코드를 이해해야 하는 상황이 발생할 수도 있다. 

즉 계층 중간중간에 함정처럼 숨겨져 있는 메서드 오버라이딩과 만나면 극단적으로 이해하기 어려운 코드가 만들어진다.

 


정적 타입 언어와 이해할 수 없는 메세지

정적 타입 언어에서는 코드를 컴파일 할때 상속 계층 안의 클래스들이 메세지를 이해할 수 있는지 여부를 판단한다. 따라서 상속 계층 전체를 탐색한 후에도 메시지를 처리할 수 있는 메서드를 발견하지 못한다면 컴파일 에러를 발생시킨다.

 

Lecture lectur = new GradeLecture(...); // Lecture에 자식클래스
lecture.unknownMessage(); // 컴파일 에러 발생하여 프로그래머에게 알린다.

 

 

동적 타입 언어와 이해할 수 없는 메세지

동적 타입 언어 역시 메시지를 수신한 객체의 클래스부터 부모 클래스 방향으로 메서드를 탐색한다. 차이점이라면 동적 타입 언어에는 컴파일 단계가 존재하지 않기 때문에 실제로 코드를 실행해보기 전에는 메시지 처리 가능 여부를 판단할 수 없다는 점이다.

 


 

self 대 super

self 참조의 가장 큰 특징은 동적이라는 점이다. self 참조는 메시지를 수신한 객체의 클래스에 따라 메서드 탐색을 위한 문맥을 실행 시점에 결정한다.

 

자식 클래스에서 부모 클래스의 구현을 재사용해야 하는 경우가 있다. 대부분의 객체지향 언어들은 자식클래스에서 부모클래스의 인스턴스 변수나 메서드에 접근하기 위해 사용할 수 있는 super 참조라는 내부 변수를 제공한다.

 

super 전송

public class GradeLecture extends Lecture {

	@Override 
	public String evaluate() {
		return super.evaluate() + ", + gradesStatistics(); 
    }    
}
public class FormattedGradeLecture extends GradeLecture {

	public FormattedGradeLecture(String name, int pass, List<Grade) grades, List<Integer) scores) { 
    	super(name, pass, grades, scores); 
    }
	
    public String formatAverage() {
		return String.format("Avg: %1.1f",super average());
	}
}

부모 클래스 부터 상위 부모로 탐색한다.

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

self전송은 동적으로 결정하지만 super전송은 컴파일 시점에 미리 결정해 놓을 수 있다.

 

지금까지 살펴본 것처럼 동적 바인딩, self참조, super 참조는 상속을 이용해 다형성을 구현하고 코드를 재사용하기 위한 가장 핵심적인 재료다.

'IT > 오브젝트' 카테고리의 다른 글

ch.15 프레임워크와 코드 재사용  (0) 2021.08.07
ch.13 서브클래싱과 서브타이핑  (0) 2021.07.31
ch11. 합성과 유연한 설계  (0) 2021.07.15
ch.10 상속과 코드 재사용  (0) 2021.07.10
ch.09 유연한 설계  (0) 2021.07.03