맨위로가기

상속 (객체 지향 프로그래밍)

"오늘의AI위키"는 AI 기술로 일관성 있고 체계적인 최신 지식을 제공하는 혁신 플랫폼입니다.
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.

1. 개요

상속은 객체 지향 프로그래밍에서 서브클래스가 슈퍼클래스의 속성과 메서드를 물려받는 개념이다. 이는 코드 재사용과 다형성을 구현하는 데 사용되며, 단일 상속, 다중 상속, 다단계 상속 등 다양한 유형이 존재한다. 상속은 서브타이핑과 유사하지만, 서브타이핑은 자료형 간의 관계를, 상속은 구현 간의 관계를 나타낼 수 있다. 상속은 설계 제약과 문제점을 야기할 수 있으며, 대안으로 합성 재사용 원칙, 믹스인, 디자인 패턴 등이 제시된다. UML에서는 일반화/특수화 관계로 표현되며, 인터페이스 구현에도 사용된다.

2. 역사

1966년 토니 호어는 공통 속성을 가지면서도 다른 종류의 태그와 필드들로 구별되는 레코드 서브클래스를 제안했다.[8] 1967년 올레-요한 달과 크리스텐 니가드는 서로 다른 클래스에 속하지만 공통 속성을 가진 객체를 허용하는 디자인을 발표했다. 이 디자인에서 공통 속성은 슈퍼클래스에 모였고, 각 슈퍼클래스는 자체적으로 슈퍼클래스를 가질 수 있었다.[9] 이 아이디어는 1968년 시뮬라 67에 처음 적용되었으며,[10][23] 이후 스몰토크, C++, 자바, 파이썬 등 여러 언어로 확산되었다.

3. 유형

상속에는 프로그래밍 언어와 패러다임에 따라 다양한 유형이 있다.[11]


  • - 상속은 다른 객체의 특성 (데이터, 프로시저, 함수, 상수, 애노테이션 등)을 계승한다는 개념이며, 계승된 객체가 어떤 성질을 가지며 어떻게 행동하는지는 완전히 임의로 결정된다. 일반적인 계승 방식은 요청된 특성을 해당 객체가 가지지 않는 경우, 자동으로 상위 객체를 검색하는 방식이며, 이는 암묵적인 위임 (delegation) 기반이라고도 불린다. 이 외에도, 인스턴스화 시 해당 유형의 상속 체인을 훑어 모든 요소를 수집하고, 동명의 중복 요소를 해결하여 하나의 실체를 생성하는 방식도 있으며, 이는 연결(concatenation) 기반이라고도 불린다.


객체의 특성을 계승하고 새로운 특성을 추가하여 객체의 기능을 확장하고, 계승되는 공통 특성을 상위 노드로 묶어 객체를 분류하는 것은 차분 프로그래밍이라고 불리며, 프로그램의 재사용성과 보수성을 높이는 데 기여한다.

상속(inheritance)과 서브타이핑 (subtyping)은 혼동되기 쉽다. 서브타이핑은 부모 객체에 대한 자식 객체의 안전한 대체/대입(substitute)을 보장하는 상속을 의미한다. 반면, 단순한 상속은 부모 객체의 특성을 단순히 계승하며, 안전한 대체/대입과는 관련이 없다. 예를 들어, 부모의 흑백 영화를 컬러 영화로 만드는 것은 대체 가능한 서브타이핑이며, 부모의 흑백 영화를 미디어 믹스 상품 판매로 연결하는 것은 대체 불가능한 상속이다. 상속에 대입 가능성(substitutability)을 넣어 서브타이핑을 만들 것을 제창하는 것이 리스코프 치환 원칙이다.

상속과 대비되는 개념으로 합성이 있다. 상속의 서브타이핑 용법의 상위 개념과 하위 개념 (Is-a)에 대해, 합성은 (Has-a) 관계를 가진다. 그러나 상속의 비 서브타이핑 용법에서는 슈퍼클래스와 서브클래스의 관계가 (Is-a도 Has-a도 아닌) 경우가 종종 있어, 합성과 구분이 중요하게 여겨진다.

차분 프로그래밍(difference coding)은 클래스 간의 공통 구성을 각 클래스의 특유 구성으로 이어받게 하여 중복 구성을 줄이고 분류 체계화를 목적으로 하는 상속의 용법이다. 클래스에 새로운 기능을 추가하는 확장 목적과 클래스의 공통 부분을 묶어 체계화하는 분류 목적으로 사용되었다.

차분 프로그래밍은 상속의 원래 용법으로, 프로그램의 재사용성과 유지보수성을 높이는 것으로 여겨졌지만, 후년에는 계층적으로 분산 배치된 데이터와 메서드를 파악하기 어려워지는 폐해가 두드러지면서 이 용법을 부정하는 경향이 강해졌다. 동시에 그 대안으로서 합성(Object composition)이 중요하게 여겨지고 있다.

서브타이핑 (subtyping)은 슈퍼클래스의 인스턴스를 서브클래스의 인스턴스로 안전하게 대체할 수 있도록 하는 상속의 사용법을 가리킨다. 기본 클래스의 변수에 파생 클래스의 인스턴스를 안전하게 대입할 수 있는 가능성(substitutability)을 보장하며, Is-a 관계라고도 한다. 서브타이핑에서는 파생 측에서의 필드 추가가 억제되고, 기본 측으로부터의 메서드 구현 상속도 억제되며, 기본 측으로부터의 메서드 정의 (메서드 시그니처) 상속이 중시된다. 파생 인스턴스가 대입된 기본 변수의 메서드명으로부터 파생 메서드 내용이 호출되는 언어 기능은 메서드 오버라이드라고 불리며, 그 기능 개념은 동적 디스패치라고 불린다. 서브타이핑은 동적 디스패치에 초점을 맞춘 상속으로 해석할 수 있다.

구체 메서드 (정의 + 구현)의 상속은 구현 상속 (implementation inheritance) 또는 코드 상속 (code inheritance)이라고 불리며, 추상 메서드 (정의만)의 상속은 인터페이스 상속 (interface inheritance)이라고 불린다.

Is-a 관계의 서브타이핑을 주체로 하는 상속 관계는 UML 클래스 다이어그램에서는 일반화/특수화 관계로, 추상 메서드만으로 구성된 순수 추상 클래스인 인터페이스와의 상속 관계는 UML 클래스 다이어그램에서는 실현/구현 관계로 투영된다.

서브타이핑의 코딩 예시는 다음과 같다.

```cpp

#include

#include

#include

class Base {

public:

virtual ~Base() {}

virtual std::string greet() const = 0;

};

class Derived : public Base {

virtual ~Derived() { std::cout << "Destructor of Derived is called." << std::endl; }

virtual std::string greet() const { return "Hello!"; }

};

int main() {

Base* b = new Derived(); // OK

std::cout << "Message: " << b->greet() << std::endl;

std::cout << "Is instance of Derived? " << std::boolalpha << (typeid(*b) == typeid(Derived)) << std::endl;

delete b;

return 0;

}

3. 1. 단일 상속

서브클래스가 하나의 슈퍼클래스로부터 특징을 상속받는 방식이다. 클래스는 다른 클래스의 속성을 획득한다.[11]
단일 상속

3. 2. 다중 상속

하나의 클래스가 둘 이상의 슈퍼클래스로부터 특징을 상속받는 경우를 다중 상속이라고 한다.[11] 비야네 스트롭스트룹은 다중 상속이 효율적으로 구현하기 매우 어렵다고 알려져 있었지만, 1982년부터 다중 상속을 고려했고 1984년에 간단하고 효율적인 구현 기술을 발견하여 이 도전을 받아들였다고 말했다.[12]

다중 상속


다중 상속은 단일 상속과 달리 슈퍼클래스의 멤버 검색이 여러 방향으로 나뉘어지므로, 어떤 멤버가 참조되는지 파악하기 어렵다는 단점이 있다. 특히 필드의 다중 상속/분산 배치는 초기에 원칙적으로 금지하는 것이 일반화되었다. 메서드는 어쩔 수 없이 허용되었기 때문에 메서드 결정 순서(MRO) 문제가 거론되었다. MRO 문제를 해결하기 위해 인터페이스 구현과 트레이트 인클루드가 도입되었으며, 이 둘은 모두 데이터 주체 클래스를 단일 상속으로 하고 메서드 주체 클래스를 다중 상속으로 하는 하이브리드 상속의 역할을 했다.

또한, 다중 상속에서 슈퍼클래스의 중복으로 인한 다이아몬드 상속 문제도 문제시되고 있다. 다이아몬드 상속 문제의 해결책으로는 C++(C++)/에펠 언어(Eiffel)에서 나온 가상 상속, 에펠 언어(Eiffel)에서 나온 이름 변경, 파이썬(Python)에서 나온 C3 선형화 등이 있다.

다중 상속과 가상 상속의 코딩 예는 다음과 같다. 동일한 클래스에서 상속하는 여러 파생 클래스를 다중 상속하여 하나의 클래스를 만들 경우, 처음 기본 클래스의 존재를 어떻게 할 것인가에 따라 가상 상속과 일반적인 다중 상속의 두 가지로 나뉜다.

```cpp

class Base {

public:

int n;

};

// 비가상 상속.

class DerivedNV1 : public Base { /* ... */ };

class DerivedNV2 : public Base { /* ... */ };

// 가상 상속.

class DerivedV1 : public virtual Base { /* ... */ };

class DerivedV2 : public virtual Base { /* ... */ };

class DerivedNV : public DerivedNV1, public DerivedNV2 { /* ... */ };

class DerivedV : public DerivedV1, public DerivedV2 { /* ... */ };

int main() {

DerivedNV nv;

//nv.n = 0; // 모호함을 해결할 수 없으므로 컴파일 에러.

nv.DerivedNV1::n = 0;

nv.DerivedNV2::n = 0;

DerivedV v;

v.n = 0; // 컴파일 에러가 아니다.

return 0;

}

```

위 예와 같은 상태는 특히 다이아몬드 상속이라고 불린다.

가상 상속이 아닌 경우, `DerivedNV`의 인스턴스에는 `DerivedNV1`의 기본 `Base::n`과 `DerivedNV2`의 기본 `Base::n`이라는 두 개의 `n`이 별도로 존재하게 된다(멤버 함수도 마찬가지). 반면, 가상 상속을 한 경우, `DerivedV`의 인스턴스에는 Base 부분은 단 하나만 존재한다. 즉, `DerivedV1`의 기본과 `DerivedV2`의 기본이 공유되고 있는 상태이다.

C++(C++)나 에펠 언어(Eiffel)와 같은 초기 객체 지향 프로그래밍 언어에서는 구현의 다중 상속이 가능했지만, 자바(Java)나 C 샤프(C#)와 같은 후발 언어에서는 구현은 단일 상속으로 제한되었고, 대신 인터페이스의 다중 상속(인터페이스의 다중 상속)이 도입되었다. 이는 구현의 다중 상속이 장점보다 단점이 더 많다고 여겨졌기 때문이다.

다중 상속의 단점은 다음과 같다.

  • 상속 관계가 복잡해지므로 전체 파악이 어려워진다.
  • 이름 충돌. 같은 이름을 여러 기본 클래스가 각각 다른 의미로 사용하고 있는 경우, 그 양쪽을 파생 클래스에서 오버라이드하는 것이 어렵다.
  • 처리계의 구현이 복잡해진다.
  • 가상 상속을 하지 않은 경우 동일한 기본 클래스가 여러 개 존재하게 된다(이것이 바람직한 경우도 있지만). 이것의 문제는, 처음에는 가상 상속을 하지 않았던 것을 나중에 가상 상속을 하고 싶어졌을 때 변경점을 찾아내는 것이 어려워진다는 것이다. 즉, 가상 상속을 사용하기 위해서는 설계를 제대로 할 필요가 있다.


그러나 다중 상속을 사용하는 것이 더 직관적인 경우가 있다는 주장도 있어, 어느 쪽이 옳다고 단정할 수 없는 상황이다.

3. 3. 다단계 상속

서브클래스가 다른 서브클래스로부터 상속되는 경우이다.
다단계 상속
그림 "다단계 상속"에서 볼 수 있듯이, 클래스가 다른 파생 클래스에서 파생되는 것은 드문 일이 아니다.[11]

클래스 ''A''는 ''파생 클래스'' ''B''의 ''기본 클래스'' 역할을 하며, 이는 다시 ''파생 클래스'' ''C''의 ''기본 클래스'' 역할을 한다. 클래스 ''B''는 ''A''와 ''C'' 사이의 상속에 대한 링크를 제공하므로 ''중간'' 기본 클래스라고 한다. ''ABC'' 체인은 ''상속 경로''라고 한다.[11]

다단계 상속을 가진 파생 클래스는 다음과 같이 선언된다:[11]

```cpp

// C++ 언어 구현

class A { ... }; // 기본 클래스

class B : public A { ... }; // A에서 파생된 B

class C : public B { ... }; // B에서 파생된 C

```

이 프로세스는 모든 수준으로 확장될 수 있다.[11]

3. 4. 계층적 상속

하나의 클래스가 둘 이상의 서브 클래스에 대한 슈퍼클래스(기본 클래스) 역할을 하는 경우이다. 예를 들어, 부모 클래스 A는 두 개의 서브 클래스 B와 C를 가질 수 있다. B와 C 모두의 부모 클래스는 A이지만, B와 C는 두 개의 별도 서브 클래스이다.[11]

3. 5. 하이브리드 상속

하이브리드 상속은 둘 이상의 상속 유형이 혼합된 형태이다. 예를 들어 클래스 A가 서브 클래스 B를 갖고, B가 다시 서브 클래스 C와 D를 갖는 경우가 이에 해당한다. 이는 다단계 상속과 계층적 상속이 혼합된 것이다.

4. 서브클래스와 슈퍼클래스

1966년 토니 호어는 공통 속성을 가졌지만 다른 종류의 태그와 필드로 구별되는 레코드 서브클래스를 제시했다. 이로부터 영향을 받은 올요한 달과 크리스텐 나이가드는 서로 다른 클래스에 속하지만 공통 속성을 가지는 객체를 허용하는 디자인을 제시했다. 이 디자인에서 공통 속성은 슈퍼클래스를 구성했으며, 슈퍼클래스는 자신의 슈퍼클래스를 가질 수 있었다. 이 아이디어는 1968년 시뮬라 67에 적용되었으며,[23] 이후 스몰토크, C++, 자바, 파이썬으로 퍼져나갔다.

서브클래스는 파생 클래스, 상속 클래스, 또는 자식 클래스라고도 불리며, 하나 이상의 다른 클래스(슈퍼클래스, 기본 클래스, 또는 부모 클래스)에서 하나 이상의 프로그래밍 언어 엔티티를 상속하는 모듈러 파생 클래스이다. 클래스 상속의 의미는 언어마다 다르지만, 일반적으로 서브클래스는 슈퍼클래스의 인스턴스 변수와 멤버 함수를 자동으로 상속한다.

파생 클래스를 정의하는 일반적인 형식은 다음과 같다.[13]



class SubClass: visibility SuperClass

{

// 서브클래스 멤버

};


  • 콜론(:)은 서브클래스가 슈퍼클래스에서 상속함을 나타낸다. 가시성(visibility)은 선택 사항이며, `private` 또는 `public`일 수 있다. 기본 가시성은 `private`이다. 가시성은 기본 클래스의 기능이 `private`하게 파생되는지 또는 `public`하게 파생되는지를 지정한다.


에펠과 같은 일부 언어에서는 클래스의 사양을 정의하는 계약도 상속된다. 슈퍼클래스는 특정한 서브클래스가 상속, 수정 및 보완할 수 있는 공통 인터페이스와 기본적인 기능을 수립한다. 서브클래스에 의해 상속된 소프트웨어는 서브클래스에서 재사용성으로 간주된다. 클래스의 인스턴스에 대한 참조는 사실 그것의 서브클래스 중 하나를 참조하는 것이며, 참조되는 객체의 실제 클래스를 컴파일 타임에 예측하는 것은 불가능하다. 수많은 다른 클래스의 객체 멤버 함수를 호출하는데에는 똑같은 인터페이스가 사용된다. 서브클래스는 슈퍼클래스의 함수를 같은 메소드 시그니처를 공유해야 하는 완전히 새로운 함수로 대체할 수도 있다.

4. 1. 상속 불가 클래스

일부 프로그래밍 언어에서는 클래스 선언에 특정 클래스 한정자를 추가하여 그 클래스가 상속 불가하다고 선언할 수 있다. 자바의 "final" 키워드 또는 C#의 "sealed" 키워드가 그 예이다. 이러한 한정자들은 클래스 선언에서 "class" 키워드와 클래스 식별자 선언 앞에 추가된다. 봉인(sealed) 클래스는 특히 개발자들이 소스 코드에 권한이 없고 미리 컴파일된 바이너리에만 권한이 있을 때, 재사용성이 제한된다.

상속 불가 클래스는 하위 클래스가 없으므로, 해당 클래스의 객체에 대한 참조나 포인터가 실제로 해당 클래스의 인스턴스를 참조하며 하위 클래스(존재하지 않음)나 상위 클래스 인스턴스(참조 유형의 업캐스팅은 타입 시스템을 위반함)의 인스턴스를 참조하지 않는다는 것을 컴파일 타임에 쉽게 추론할 수 있다. 참조되는 객체의 정확한 유형이 실행 전에 알려지기 때문에, 후기 바인딩(동적 디스패치) 대신 초기 바인딩(정적 디스패치)을 사용할 수 있다. 후기 바인딩은 가상 메서드 테이블 조회(프로그래밍 언어가 다중 상속을 지원하는지 또는 단일 상속만 지원하는지에 따라 다름)를 하나 이상 필요로 한다.

4. 2. 오버라이드 할 수 없는 메소드

일부 프로그래밍 언어에서는 클래스 선언에 특정 클래스 한정자를 추가하여 해당 클래스를 상속 불가능하도록 선언할 수 있다. 자바의 `final` 키워드나 C#의 `sealed` 키워드가 그 예이다. 이러한 한정자들은 클래스 선언에서 `class` 키워드와 클래스 식별자 선언 앞에 추가된다.

클래스가 봉인(sealed) 또는 최종화(finalized)될 수 있는 것처럼, 메소드 선언에도 메소드가 오버라이드(즉, 서브클래스에서 같은 이름과 타입 시그니처를 가진 새로운 함수로 대체)되는 것을 막는 메소드 한정자를 추가할 수 있다. 프라이빗 메소드는 해당 멤버 함수가 속한 클래스가 아닌 다른 클래스에서는 접근할 수 없으므로 오버라이드할 수 없다. 자바의 `final` 메소드나 C#의 `sealed` 메소드도 오버라이드할 수 없다.

4. 3. 가상 메소드

수퍼클래스 메소드가 가상 메소드인 경우, 수퍼클래스의 메소드 호출은 동적 디스패치된다. 일부 언어(예: C++)에서는 특정 메소드가 가상으로 선언되어야 하고, 기타 언어(예: 자바)에서는 모든 메소드가 가상이다. 비가상 메소드의 호출은 항상 정적으로 디스패치된다 (즉, 함수 호출의 주소는 컴파일 타임에 결정된다). 정적 디스패치는 동적 디스패치보다 빠르고 인라인 확장과 같은 최적화를 가능하게 한다.

5. 가시성

C++에서 확립된 용어를 사용하여 클래스 파생 시 주어진 가시성에 따라 어떤 변수와 함수가 상속되는지는 다음 표와 같다.[14]

기본 클래스 가시성파생 클래스 가시성
private 파생protected 파생public 파생
style="vertical-align:top;"|style="vertical-align:top;"|style="vertical-align:top;"|style="vertical-align:top;"|



캡슐화의 가시성(public/protected/package/private)에 따라 각 슈퍼클래스 멤버의 상속 여부가 선택되는 것은 서브타이핑에 대한 상속의 큰 특징이다. private 멤버는 서브클래스에 상속되지 않으며, package 멤버는 외부 패키지의 서브클래스에 상속되지 않는다.

상속의 가시성은 슈퍼클래스 멤버(필드/메서드)의 가시성에 추가적인 제약을 가하는 기능으로, 다음 세 단계가 있다.

# public 상속 - 슈퍼클래스의 멤버 가시성을 그대로 상속한다.

# protected 상속 - 슈퍼클래스의 public 멤버를 protected 멤버로 낮춰서 상속한다.

# private 상속 - 슈퍼클래스의 public/protected 멤버를 private 멤버로 낮춰서 상속한다.

이는 C++에서 도입되었지만, 후속 OOP 언어에서는 거의 채택되지 않았다.

6. 응용

상속은 둘 이상의 클래스를 서로 연관시키는 데 사용된다.

6. 1. 오버라이딩 (Overriding)

메서드 오버라이딩의 예시


많은 객체 지향 프로그래밍 언어는 클래스 또는 객체가 상속받은 측면(일반적으로 동작)의 구현을 대체하도록 허용한다. 이 과정을 ''오버라이딩''이라고 한다. 오버라이딩은 복잡성을 야기한다. 즉, 상속된 클래스의 인스턴스가 어떤 버전의 동작을 사용할 것인가? 자체 클래스의 일부인 버전인가, 아니면 부모(기본) 클래스의 버전인가? 답은 프로그래밍 언어마다 다르며, 일부 언어는 특정 동작이 오버라이딩되지 않도록 하고 기본 클래스에 정의된 대로 동작하도록 지정하는 기능을 제공한다. 예를 들어, C#에서는 기본 메서드 또는 속성이 virtual, abstract 또는 override 한정자로 표시된 경우에만 하위 클래스에서 오버라이딩될 수 있는 반면, Java와 같은 프로그래밍 언어에서는 다른 메서드를 호출하여 다른 메서드를 오버라이딩할 수 있다.[15] 오버라이딩의 대안은 상속된 코드를 숨기는 것이다.

6. 2. 코드 재사용

구현 상속은 하위 클래스가 기본 클래스의 코드를 재사용하는 메커니즘이다. 기본적으로 하위 클래스는 기본 클래스의 모든 연산을 유지하지만, 일부 또는 모든 연산을 재정의하여 기본 클래스 구현을 자체 구현으로 대체할 수 있다.

다음 Python 예제에서 `SquareSumComputer` 및 `CubeSumComputer` 하위 클래스는 기본 클래스 `SumComputer`의 `transform()` 메서드를 재정의한다. 기본 클래스는 두 정수 사이의 제곱의 합을 계산하는 연산으로 구성된다. 하위 클래스는 숫자를 제곱으로 변환하는 연산을 숫자를 각각 제곱 및 세제곱으로 변환하는 연산으로 대체하여 기본 클래스의 모든 기능을 재사용한다. 따라서 하위 클래스는 두 정수 사이의 제곱/세제곱의 합을 계산한다.



다음은 Python의 예이다.

```python

class SumComputer:

def __init__(self, a, b):

self.a = a

self.b = b

def transform(self, x):

raise NotImplementedError

def inputs(self):

return range(self.a, self.b)

def compute(self):

return sum(self.transform(value) for value in self.inputs())

class SquareSumComputer(SumComputer):

def transform(self, x):

return x * x

class CubeSumComputer(SumComputer):

def transform(self, x):

return x * x * x

```

대부분의 경우, 코드 재사용만을 목적으로 하는 클래스 상속은 인기가 떨어졌다. 주요 관심사는 구현 상속이 다형성 대체 가능성을 보장하지 않는다는 것이다. 즉, 재사용 클래스의 인스턴스가 상속된 클래스의 인스턴스로 반드시 대체될 수 있는 것은 아니다. 대안적인 기술인 명시적 위임은 더 많은 프로그래밍 노력을 필요로 하지만, 대체 가능성 문제를 피할 수 있다. C++에서 private 상속은 대체 가능성 없이 '구현 상속' 형태로 사용될 수 있다. public 상속이 "is-a" 관계를 나타내고 위임이 "has-a" 관계를 나타내는 반면, private (및 protected) 상속은 "is implemented in terms of" 관계라고 생각할 수 있다.[16]

차분 프로그래밍(difference coding)은 클래스 간의 공통 구성을 각 클래스의 특유 구성으로 이어받게 하여 중복 구성을 줄이고 분류 체계화를 목적으로 하는 상속의 용법이다. 이는 클래스에 새로운 기능을 추가하는 간편한 클래스 확장 목적과 클래스의 공통 부분을 묶어 체계화하는 클래스 분류 목적 모두에 사용되었다.

차분 프로그래밍은 상속의 원래 용법으로, 프로그램의 재사용성과 유지보수성을 높이는 것으로 여겨졌지만, 후년에는 계층적으로 분산 배치된 데이터와 메서드를 파악하기 어려워지는 폐해가 두드러지면서 이 용법을 부정하는 경향이 강해졌다. 동시에 그 대안으로서 합성이 중요하게 여겨지고 있다.

7. 상속 대 서브타이핑

상속은 서브타이핑과 유사하지만 구별되는 개념이다.[4] 서브타이핑은 주어진 자료형을 다른 유형 또는 추상화로 대체할 수 있도록 하며, 언어 지원에 따라 암시적으로 또는 명시적으로 서브타입과 기존 추상화 간의 ''is-a'' 관계를 설정한다.

상속을 서브타이핑 메커니즘으로 지원하지 않는 프로그래밍 언어에서 기본 클래스와 파생 클래스 간의 관계는 자료형 간의 관계가 아닌 구현 간의 관계(코드 재사용을 위한 메커니즘)일 뿐이다. 상속은 상속을 서브타이핑 메커니즘으로 지원하는 프로그래밍 언어에서도 반드시 행위적 서브타이핑을 수반하지는 않는다. 부모 클래스가 예상되는 컨텍스트에서 해당 객체를 사용했을 때 잘못된 동작을 하는 클래스를 파생시키는 것이 완전히 가능하다. 리스코프 치환 원칙을 참조하라.[18]

서브타이핑이란, 슈퍼클래스의 인스턴스를 서브클래스의 인스턴스로 안전하게 대체할 수 있도록 하는 상속의 사용법을 가리킨다. 기본 클래스의 변수에 파생 클래스의 인스턴스를 안전하게 대입할 수 있는 가능성(substitutability)을 보장한다. 이는 Is-a 관계라고도 한다.

요약하면, 서브타이핑은 슈퍼클래스의 인스턴스를 서브클래스의 인스턴스로 안전하게 대체할 수 있는 관계를 의미하며, 이는 리스코프 치환 원칙에 따른 것이다. 반면, 단순한 상속은 부모 객체의 특성을 단순히 계승하는 데 중점을 두고, 안전한 대체/대입에는 신경 쓰지 않는다.

일부 언어(예: Go)에서는 상속과 서브타이핑을 분리하여 지원한다.

8. 설계 제약


  • 단일성: 단일 상속을 사용하면 하위 클래스는 하나의 상위 클래스에서만 상속받을 수 있다. 예를 들어, 객체는 또는 중 하나일 수 있지만 둘 다일 수는 없다. 다중 상속을 사용하면 이 문제를 부분적으로 해결할 수 있지만, 대부분의 구현에서는 각 상위 클래스에서 한 번만 상속받을 수 있다.
  • 정적: 객체의 상속 계층 구조는 객체의 유형이 선택되고 시간에 따라 변경되지 않는 인스턴스화 시점에 고정된다. 예를 들어, 상속 그래프는 객체가 상위 클래스의 상태를 유지하면서 객체가 되는 것을 허용하지 않는다.
  • 가시성: 클라이언트 코드가 객체에 접근할 때, 일반적으로 객체의 모든 상위 클래스 데이터에 접근할 수 있다. 많은 최신 언어는 "protected" 접근 제어자를 제공하여 상속 체인 외부의 코드가 접근하는 것을 허용하지 않으면서 하위 클래스가 데이터에 접근할 수 있도록 한다.


합성 재사용 원칙은 상속의 대안으로, 기본 클래스 계층 구조에서 동작을 분리하고 모든 비즈니스 도메인 클래스에 필요한 특정 동작 클래스를 포함시켜 다형성 및 코드 재사용을 지원한다.

9. 문제점 및 대안

앨런 홀럽은 구현 상속의 주요 문제점으로 불필요한 결합을 초래하는 "취약한 기본 클래스 문제"를 지적했다.[19] 기본 클래스가 수정되면 서브클래스에서 예상치 못한 동작 변경이 발생할 수 있다. 인터페이스를 사용하면 구현이 아닌 API만 공유되므로 이 문제를 피할 수 있다. 즉, "상속은 캡슐화를 깨뜨린다"는 것이다.[19]

자바(Java) 개발자인 제임스 고슬링도 구현 상속에 반대하며, 자바를 다시 설계한다면 구현 상속을 포함시키지 않을 것이라고 말했다.[20]

복잡하거나 충분히 성숙하지 않은 설계에서 상속을 사용하면 요요 문제가 발생할 수 있다. 1990년대 후반에는 상속이 프로그램을 구조화하는 주요 방식이었기 때문에, 시스템 기능이 확장됨에 따라 개발자들은 코드를 더 많은 상속 계층으로 나누는 경향이 있었다. 개발팀이 상속의 여러 계층을 단일 책임 원칙과 결합하면, 실제 코드는 1~2줄에 불과한 매우 얇은 계층이 많이 생성되었다. 이로 인해 디버깅할 계층을 결정하기 어려워져 디버깅 난이도가 높아졌다.

또한, 상속은 서브클래스가 코드에 정의되어야 한다는 제약이 있어 프로그램 사용자가 런타임에 새로운 서브클래스를 추가할 수 없다. 개체-컴포넌트-시스템과 같은 다른 디자인 패턴을 사용하면 프로그램 사용자가 런타임에 엔티티의 변형을 정의할 수 있다.

이러한 문제점의 대안으로 합성(Object composition)이 중요하게 여겨지고 있다.

10. 믹스인 (Mixin)

믹스인(Mix-in)은 다중 상속 문제의 해결책으로 제시된 또 다른 상속 방법론이다. 메서드 집합을 상속하여 클래스에 기능을 추가(주입)하는 것을 목표로 한다. 이때 메서드 집합과 클래스 사이에는 일반화/특수화 관계가 없으며, 다중 상속을 전제로 한다. 이러한 메서드 집합은 트레이트로 간주되는 경우가 많으며, 모듈, 프로토콜, 롤(Role)과 같은 형태도 있다. 트레이트 상속은 포함(include)이라고 부르는 것을 선호하며 다중 상속을 전제로 한다. 믹스인은 이들을 모두 아우르는 방법론으로서의 용어가 되었다.

믹스인 상속의 트레이트(독립 메서드를 모아놓은 모듈)는 인터페이스(추상 메서드를 모아놓은 클래스)와 마찬가지로 다중 상속을 전제로 하기 때문에 자주 비교된다. 둘의 차이점은 다음과 같다.

항목인터페이스 상속믹스인 상속
메서드 종류추상 메서드독립 메서드
구현 위치상속 대상 클래스트레이트
알고리즘 기술개별 클래스의 메서드에 분산 기술하나의 메서드에 일괄 기술
데이터 멤버없음있음
this 참조암묵적 사용 가능명시적 인자 전달 또는 관련 타입 기능 필요
타입 시스템명목적 타이핑구조적 타이핑


11. UML에서의 상속

통합 모델링 언어(UML)의 클래스 다이어그램에서 Is-a 관계의 서브타이핑을 주체로 하는 상속 관계는 '''일반화/특수화'''(generalization/specialization) 관계로 표현된다. 즉, 서브 클래스에서 슈퍼 클래스를 '''일반화''', 슈퍼 클래스에서 서브 클래스를 '''특수화'''라고 부른다.

추상 메서드만으로 구성된 순수 추상 클래스는 인터페이스라고 불리며, 인터페이스 상속 관계는 UML 클래스 다이어그램에서 '''실현/구현'''(realization/implementation) 관계로 표현된다. 클래스에서 인터페이스를 상속하는 것은 '''구현'''이라고 한다.

믹스인 및 차분 프로그래밍 용법은 UML 클래스 다이어그램에서 명시적으로 다루어지지 않는다.

참조

[1] 웹사이트 Designing Reusable Classes https://www.cse.msu.[...] 1991-08-26
[2] 서적 Conference proceedings on Object-oriented programming systems, languages and applications - OOPSLA '89 1989
[3] 서적 Advanced Methods and Deep Learning in Computer Vision Elsevier Science 2021
[4] 학회 Inheritance is not subtyping
[5] 기술보고서 Typeful Programming
[6] 학회 A study of the fragile base class problem http://extras.spring[...] Springer 2015-08-28
[7] 학회 What programmers do with inheritance in Java https://www.cs.auckl[...] Springer
[8] 기술보고서 Record Handling http://archive.compu[...] 1966
[9] 학회 Class and subclass declarations https://www.ub.uio.n[...] Norwegian Computing Center 1967-05
[10] 서적 From Object-Orientation to Formal Methods 2004
[11] 웹사이트 C++ Inheritance https://www.cs.nmsu.[...] 2018-05-16
[12] 서적 The Design and Evolution of C++ Pearson 1994
[13] 서적 The complete reference C++ https://archive.org/[...] Tata McGraw Hill
[14] 서적 Object Oriented Programming With C++ Tata McGraw Hill
[15] 웹사이트 override(C# Reference) http://msdn.microsof[...]
[16] 웹사이트 GotW #60: Exception-Safe Class Design, Part 2: Inheritance http://www.gotw.ca/g[...] Gotw.ca 2012-08-15
[17] 서적 Mastering C++ Tata McGraw Hill Education Private Limited
[18] 서적 Concepts in programming language https://archive.org/[...] Cambridge University Press
[19] 간행물 Evolution of object behavior using context relations http://www.ccs.neu.e[...]
[20] 웹사이트 Why extends is evil http://www.javaworld[...] 2003-08-01
[21] 학회 Designing an object-oriented programming language with behavioural subtyping https://www.research[...]
[22] 웹사이트 継承とプロトタイプチェーン - JavaScript https://developer.mo[...] 2022-09-17
[23] 서적 Hardware Verification with C++: A Practitioner’s Handbook Springer



본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.

문의하기 : help@durumis.com