Skip to main content

12. 다형성

  • 타입 계층은 객체지향 프로그래밍의 중요한 특성 중 하나인 다형성의 기반을 제공합니다.

01. 다형성#

  • 다형성은 여러 타입을 대상으로 동작할 수 있는 코드를 작성할 수 있는 방법입니다.

다형성

  • 매개변수 다형성 : 제네릭 프로그래밍과 관련이 높으며, 클래스의 인스턴스 변수나 메서드의 매개변수 타입을 임의의 타입으로 선언한 후 사용하는 시점에 구체적인 타입으로 지정하는 방식
  • 포함 다형성 : 메시지가 동일하더라도 수신한 객체의 타입에 따라 실제로 수행되는 행동이 달라지는 능력, 포함 다형성은 서브타입(Subtype) 다형성이라고도 불립니다.
  • 오버로딩 다형성 : 하나의 클래스 안에 동일한 이름의 메서드가 존재하는 경우
  • 강제 다형성 : 언어가 지원하는 자동적인 타입 변환이나 사용자가 직접 구현한ㄷ 타입 변환을 이용해 동일한 연산자를 다양한타입에 사용할 수 있는 방식

02. 상속의 양면성#

  • 상속의 목적은 코드 재사용이 아니라, 상속은 프로그램을 구성하는 개념들을 기반으로 다형성을 자능하게 하는 타입 계층을 구축하기 위한 것입니다.

상속을 사용한 강의 평가#

  • 자식 클래스 안에 상속받은 메서드와 동일한 시그니처의 메서드를 재정의해서 부모 클래스의 구현을 새로운 구현으로 대체하는 것을 메서드 오버라이딩이라고 부릅니다.
  • 부모 클래스에서 정의한 메서드와 이름은 동일하지만 시그니처는 다른 메서드를 자식 클래스에 추가하는 것을 메서드 오버로딩이라고 부릅니다.

데이터 관점의 상속#

  • 데이터 관점에서 상속은 자식 클래스의 인스턴스 안에 부모 클래스의 인스턴스를 포함하는 것으로 볼 수 있습니다.
  • 따라서 자식 클래스의 인스턴스는 자동으로 부모 클래스에서 정의한 모든 인스턴스 변수를 내부에 포함하게 되는 것입니다.

행동 관점의 상속#

  • 데이터 관점의 상속이 자식 클래스이 인스턴스 안에 부모 클래스의 인스턴스를 포함하는 개념이라면 행동 관점의 상속은 부모 클래스가 정의한 일부 메서드를 자식 클래스의 메서드로 포함시키는 것을 의미합니다.

03. 업캐스팅과 동적 바인딩#

같은 메시지, 다른 메시지#

  • 업캐스팅 : 부모 클래스 타입으로 선언된 변수에 자식 클래스의 인스턴스를 할당하는 것이 가능합니다.
  • 동적 바인딩 : 선언된 변수의 타입이 아니라 메시지를 수신하는 객체의 타입에 따라 실행되는 메서드가 결정됩니다. 이는 객체지향 시스템이 메시지를 처리할 적절한 메서드를 컴파일 시점이 아니라 실행 시점에 결정하기 때문에 가능합니다.

업캐스팅#

  • 상속을 이용하면 부모 클래스의 퍼블릭 인터페이스가 자식 클래스의 퍼블릭 인터페이스에 합쳐지기 때문에 부모 클래스의 인스턴스에게 전송할 수 있는 메시지를 자식 클래스의 인스턴스에게 전송할 수 있습니다.
  • 부모 클래스의 인스턴스를 자식 클래스 타입으로 변환하기 위해서는 명시적인 타입 캐스팅이 필요한 데 이를 다운캐스팅(downcasting) 이라고 부릅니다.

동적 바인딩#

  • 컴파일타임에 호출할 함수를 결정하는 방식을 정적 바인딩(static binding) 이라고 부릅니다.
  • 실행될 메서드를 런타임에 결정하는 방식을 동적 바인딩(dynamic binding) 이라고 부릅니다.

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

  • 객체지향 시스템은 다음 규칙에 따라 실행할 메서드를 선택합니다.
    • 메시지를 수신한 객체는 먼저 자신을 생성한 클래스에 적합한 메서드가 존재하는지 검사합니다. 존재하면 메서드를 실행하고 탐색을 종료합니다.
    • 메서드를 찾지 못했다면 부모 클래스에서 메서드 탐색을 계속합니다. 이 과정은 적합한 메서드를 찾을 때까지 상속 계층을 따라 올라가며 계속됩니다.
    • 상속 계층의 가장 최상위 클래스에 이르렀지만 메서드를 발견하지 못한 경우 예외를 발생시키며 탐색을 중단합니다.
  • self 참조(self reference) 는 객체가 메시지를 수신하면 컴파일러는 self 참조라는 임시 변수를 자동으로 생성한 후 메시지를 수신한 객체를 가리키도록 설정합니다.
  • 동적 메서드 탐색은 두 가지 원리로 구성된다는 것을 알 수 있습니다.
    • 첫 번째 원리는 자동적인 메시지 위임입니다.
    • 두 번째 원리는 메서드를 탐색하기 위해 동적인 문맥을 사용한다는 것입니다.

자동적인 메시지 위임#

  • 자식 클래스에서 부모 클래스의 방향으로 자동으로 메시지 처리가 위이모디기 때문에 자식 클래스에서 어떤 메서드를 구현하고 있느냐에 따라 부모 클래스에 구현된 메서드의 운명이 결정되기도 합니다.
  • 동일한 시그니처를 가지는 자식 클래스의 메서드는 부모 클래스의 메서드를 감추지만 이름만 같고 시그니처가 완전히 동일하지 않은 메서드들은 상속 계층에 걸쳐 사이좋게 공존할 수도 있습니다. 이가 메서드 오버로딩입니다.

메서드 오버라이딩#

  • 자식 클래스가 부모 클래스의 메서드를 오버라이딩하면 자식 클래스에서 부모 클래스로 향하는 메서드 탐색 순서 때문에 자식 클래스의 메서드가 부모 클래스의 메서드를 감추게 됩니다.

메서드 오버로딩#

  • 동적 메서드 탐색과 관련된 규칙이 언어마다 다를 수 있습니다.

동적인 문맥#

  • self 참조가 가르키는 객체의 타입을 변경함으로써 객체가 실행될 문맥을 동적으로 바꿀 수 있습니다.
  • 그러나 이를 잘못 사용하여 깊은 상속 계층과 메서드 오버라이딩을 만나면 극단적으로 이해하기 어려운 코드가 만들어집니다.

이해할 수 없는 메시지#

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

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

  • 동적 타입 언어에는 컴파일 단계가 존재하지 않기 때문에 실제로 코드를 실행해보기 전에는 메시지 처리 가능 여부를 판단할 수 없다는 점이 있습니다.
  • 동적 타입 언어의 이러한 동적인 특성과 유연성은 코드를 이해하고 수정하기 어렵게 만들뿐만 아니라 디버깅 과정을 복잡하게 만들기도 합니다.

self 대 super#

  • 보통 자식 클래스에서 부모 클래스의 인스턴스 변수나 메서드에 접근하기 위해 사용할 수 있는 super 참조라는 내부 변수를 제공합니다.
  • 동적 바인딩, self 참조, super 참조는 상속을 이용해 다형성을 구현하고 코드를 재사용하기 위한 가장 핵심적인 재료입니다.

05. 상속 대 위임#

위임과 self 참조#

  • 자신이 수신한 메시지를 다른 객체에게 동일하게 전달해서 처리를 요청하는 것을 위임(delegation) 이라고 부릅니다.
  • 위임이 객체 사이의 동적인 연결 관계를 이용해 상속을 구현하는 방법입니다.
  • 클래스 기반의 객체지향 언어가 클래스 사이의 메시지 위임을 자동으로 처리해주는 것처럼 프로토타입 기반의 객체지향 언어는 객체 사이의 메시지 위임을 자동으로 처리해줍니다.

프로토타입 기반의 객체지향 언어#

  • 클래스가 존재하지 않고 오직 객체만 존재하는 프로토타입 기반의 객체지향 언어에서 상속을 구현하는 유일한 방법은 객체 사이의 위임을 이용하는 것입니다.
  • 메서드를 탐색하는 과정은 클래스 기반 언어의 상속과 거의 동일합니다.
  • 객체지향은 객체를 지향하는 것입니다.
  • 클래스 없이도 객체 사이의 협력 관계를 구축하는 것이 가능하며 상속 없이도 다형성을 구현하는 것이 가능합니다.
  • 중요한 부분은 클래스 기반의 상속과 객체 기반의 위임 사이에 기본 개념과 메커니즘을 공유한다는 점입니다.
Last updated on