본문 바로가기

Study/오프젝트

12) 다형성

11장 에서는 코드 재사용을 목적으로 상속을 사용하면 변경하기 어렵고 유연하지 못한 설계가 될 확률이 높다. 상속의 목적은 코드 재사용이 아니라 타입 계층을 구조화하기 위해 사용해야 한다. 이번 장에서는 상속의 관점에서 다형성이 구현되는 기술적인 메커니즘을 살펴보자.

 

1. 다형성

다형성이란 이름 그대로 '다양한 형태'를 가질 수 있다는 것을 의미한다. 즉 다양한 타입의 객체를 하나의 인터페이스나 클래스를 통해 처리할 수 있게 된다는 말이다. 다형성은 그림과 같이 분류할 수 있다.

  • 오버로딩 다형성 : 하나의 클래스 안에 메서드 이름이 같지만 매개변수의 타입이나 개수가 다른 경우
  • 강제 다형성 : 특정 타입의 값을 다른 타입으로 자동 변환하는 것
    • ex ) 정수를 실수로 변환
  • 매개변수 다형성 : 일반적으로 제네릭 프로그래밍과 관련이 높으며, 함수나 타입이 여러 형태의 매개변수를 받을 수 있게 한다. 
    • ex ) ArrayList<T>
  • 포함 다형성 : 하위 클래스가 상위 클래스 역할을 할 수 있는것을 의미. 이를 통해 오버라이딩이 가능하며 동일한 코드를 재정의 해서 사용

 

2. 상속의 양면성

앞서 말한것 처럼 상속의 목적은 코드 재사용이 아니라 프로그램을 구성하는 개념들을 기반으로 다형성을 가능하게 하는 타입 계층을 구축하기 위한 것이다. 데이터와 행동이라는 두 가지 관점에서의 상속이 가지는 특성을 알아보자.

 

데이터 관점의 상속 : 상속을 이용하면 부모 클래스에서 정의한 모든 데이터를 자식 클래스의 인스턴스에 자동으로 포함시킬 수 있다.
행동 관점의 상속 : 데이터뿐만 아니라 부모 클래스에서 정의한 일부 메서드 역시 자동으로 자식 클래스에 포함시킬 수 있다.

 

 

3. 업캐스팅과 동적 바인딩

코드 안에서 선언된 참조 타입과 무관하게 실제 메시지를 수신하는 객체의 타입에 따라 실행되는 메서드가 달라질 수 있는 것은 업캐스팅과 동적 바인딩 메커니즘이 작용되기 때문이다.

 

3.1 업캐스팅, 다운캐스팅

3.1.1 업캐스팅

자식 클래스의 인스턴스가 부모 클래스의 참조 변수에 할당될 때 발생

서로 다른 클래스의 인스턴스를 동일한 타입에 할당하는 것을 가능하게 해줌으로서 부모 클래스에 작성된 코드를 수정하지 않고도 자식 클래스에 적용이 가능하다.

class Parent { }
class Child extends Parent { }

Parent p = new Child();

 

3.1.2 다운캐스팅

부모 클래스의 참조 변수가 자식 클래스의 참조 변수에 할당될 때 발생

아래와 같이 명시적인 타입 캐스팅이 필요하다.

class Parent { }
class Child extends Parent { }
Parent p = new Child();

Child c = (Child)p;

 

3.1.3 동적 바인딩

객체지향 언어에서는 메시지를 수신했을 때 실행될 메서드가 런타임에 결정된다. 만약 animal.shout()메서드가 있다고 생각해보자. 그럼 동물 클래스의 인스턴스인지를 알아야지 해당 클래스의 메서드 shout()를 실행할 수 있을 것이다. 이처럼 실행될 메서드를 런타임에 결정하는 방식동적 바인딩이라고 한다. 반대로 정적 바인딩컴파일 타임에 호출할 함수를 결정하는 방식이다.

 

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

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

  • 메시지를 수신한 객체는 자신을 생성한 클래스에 적합한 메서드가 있는지 검사. 존재하면 해당 메서드를 실행후 탐색을 종료
  • 메서드를 찾지 못했다면 부모 클래스에서 탐색. 적합한 메서드를 찾을 때 까지 상속 계층을 따라 탐색
  • 최상위 클래스에 까지 발견하지 못한 경우 예외를 발생시킨후 탐색을 종료

뭐 설명은 이렇게 되있지만 쉽게말하면 해당 메서드가 없으면 점점 부모 클래스를 탐색한다는 말이다. 여기서 중요한 변수가 있다. self 참조가 있다. Java에서는 self 참조가 없고 this가 역할을 대신한다. self 참조는 현재의 인스턴스를 참조하는 키워드다.

 

메서드 탐색은 두 가지 원리로 구성된다. 첫 번째 원리는 자동적인 메시지 위임이다. 자식 클래스는 자신이 이해할 수 없는 메시지를 전송받은 경우 상속 계층을 따라 부모 클래스에게 처리를 위임한다. 두 번째 원리는 메서드를 탐색하기 위해 동적인 문맥을 사용한다는 것이다. 어떤 메서드를 실행할지를 결정하는 것은 컴파일 시점이 아닌 실행 시점에 이뤄지며, 메서드를 탐색하는 경로는 self 참조를 이용해 결정한다. 여기서 중요한 것은 현재 클래스의 메서드를 호출하는 것이 아니라 현재 객체(self 참조)에게 메시지를 전송하는 것이다.

 

'Study > 오프젝트' 카테고리의 다른 글

14) 일관성 있는 협력  (0) 2024.02.04
부록 A) 계약에 의한 설계  (0) 2024.02.01
11) 합성과 유연한 설계  (0) 2024.01.17
10) 상속과 코드 재사용  (0) 2024.01.10
09) 유연한 설계  (1) 2024.01.06