프로그램에서의 상속은 부모의 재화를 자손에게 물려주는 일상 생활의 상속의 개념과 부모의 생김새, 성격, 체질 등이 다음 세대에 전달되는 유전(heredity)의 개념과 유사하며 특히 유전의 개념에 더 가깝다.
우리는 유전을 통해 부모의 생김새, 성격, 체질을 선천적으로 물려받고, 환경에 적응하기 위해 필요없는 기능은 퇴화시키고 필요한 기능은 추가되거나 강화하는 과정으로 진화를 하였다.
상속(inheritance)은 캡슐화(capsulation), 추상화(abstraction), 다형성(polymorphism)과 함께 객체지향 프로그래밍의 주요 개념 중 하나이다. 객체지향 프로그래밍에서는 우리 일상 생활의 모든 대상을 클래스로 모델링하고 각 대상을 컴퓨터 내에서 데이터와 기능을 가진 객체(object)로 구현한다.
객체지향 프로그램은 여러 종류의 클래스와 객체로 이루어지며 프로그램을 구성하는 클래스 간, 객체 간에 다양한 관계(relationship)가 존재한다.
프로그램 내의 객체들은 독립적으로 홀로 동작되기 보다는 다른 객체들과 다양한 형태의 관계를 동적으로 맺고 있다.
객체들 간의 관계는 지속될 수도 있고 프로그램이 실행되는 동안 변경될 수도 있다.
객체 간의 관계는 크게 사용(use-a
) 관계와 포함(has-a
) 관계가 있다.
사용 관계는 논리적인 관계로 가장 일반적인 관계이다.
사람은 도구를 사용한다.
의 문장에서 사람
객체와 도구
객체의 관계는 사용 관계에 해당한다.
사용 관계는 클래스의 멤버 변수가 아닌 다른 클래스의 객체를 메소드의 매개변수로 전달받아 사용하거나 메소드 내에서 다른 클래스의 객체를 생성하여 이용하는 경우에 해당한다.
사용 관계를 객체지향 프로그램에서 사용하기 위해서 사람
은 Person
클래스로 도구
는 Tool
클래스로 다음과 같이 설계된다.
class Person {
public:
utilize(const Tool& tool) {
// 도구를 사용하는 행위를 구현
}
};
class Camera {
public:
Picture* takePicture() {
Picture* picture = new Picture();
// Picture 클래스의 객체인 picture 처리 기능 구현
return picture;
}
};
객체가 다른 객체를 멤버 변수로 유지하는 관계이다.
포함 관계
는 자동차는 차체, 핸들, 타이어, 오디오, 의자 등으로 구성된다.
라는 문장에서 자동차
객체는 차를 구성하는 여러 객체들을 멤버 변수로 포함한다.
즉 기존 클래스들을 조립하여 새로운 클래스를 만드는 관계이다.
포함 관계는 관계의 범위를 파악해야 하며 이에 따라 구현 방법을 달라하게 된다.
그러므로 포함해야할 객체가 하나인지 여러개인지 범위(multiplicity)를 파악해야 한다
(예: 0..1(0 또는 1개), 0..* (0 이상), 1..* (1 이상) 등)
포함 관계는 크게 부분전체(part-whole) 관계와 연관(association)관계로 구분되며 부분전체 관계는 집합(aggregation) 관계와 복합(composition) 관계로 구분된다.
집합 관계와 복합 관계는 포함하는 객체가 포함되는 객체의 생명주기에 관여하는가 아닌가의 차이이다.
- 복합(composition) 관계: 포함하는 객체가 제거되면 포함되는 객체도 함께 제거되어야 하는 관계 (예:아파트와 방, 자동차와 핸들)
- 집합(aggregation) 관계: 포함하는 객체가 제거되어도 포함되는 객체가 제거되지 않는 관계 (예:학과와 학생)
// 복합(composition) 관계
class Car
{
private:
Handle handle;
public:
Car(String handleType) {
this.handle = Handle.getInstance(handleType);
}
};
// 집합(aggregation) 관계
class Department
{
private:
Student student;
public:
Department(Student& student){
this.student = student;
}
};
클래스 간의 관계는 객체 간의 관계와 달리 정적(static)이다. 프로그램 작성 시 관계가 설정되면 실행 중에 변화하지 않는다. 클래스 간의 관계로는 상속과 구체화가 있다.
구체화 관계는 상속과 달리 논리적으로 관련이 없으나 멤버 함수 중 같은 이름을 가지는 것을 하나로 묶어지는 관계이다. C++에서 이는 추상클래스로 구현된다.
객체지향 프로그램에서 상속은 한 클래스를 이용하여 해당 클래스를 특수화한 새 클래스를 정의하는 것으로
예로 동물
과 개
또는 고양이
와의 관계를 보면 동물
클래스를 특수화하여 개
, 고양이
클래스를 정의할 수 있다.
동물
은 개
와 고양이
클래스의 부모(parent/base/super) 클래스라고 한다.
개
와 고양이
클래스는 동물
클래스의 자식(child/derived/sub) 클래스라고 한다.
상속은 객체지향 프로그래밍의 특징인 다형성(polymorphism)을 이용하여 코드 작성의 중복을 줄여주고 코드를 재사용할 수 있게 한다.
앞에서 동물
클래스와 개
,고양이
클래스의 관계는 부모 클래스와 자식 클래스 간에 is-a 관계
가 있는 경우에만 상속을 하게된다. 즉 **개(자식클래스)는 동물(부모클래스)이다.**라는 관계가 성립한다.
객체지향 프로그래밍에서 상속은 이미 정의된 클래스의 데이터와 기능을 이어 받고 필요한 데이터와 기능을 추가하는 것으로 이미 작성되어 검증된 코드(클래스의 정의)를 재사용할 수 있어 신뢰성이 보장된 프로그램을 쉽게 개발하고 유지 보수할 수 있도록 한다.
객체지향 프로그래밍에서 상속은 존재하는 클래스의 멤버 변수와 함수를 물려 받는 것이다. 자식 클래스는 상속받은 모든 멤버 함수에 대해서 다음 중 하나로 처리를 하여야 한다.
- 상속된 멤버 함수를 그대로 사용한다.
- 새로운 기능을 가진 멤버 함수를 재정의한다 (멤버 함수 오버라이딩: member function overridng)
- 기능이 필요없는 경우 사용할 수 없도록 한다. (내용이 없는 빈 멤버 함수를 재 정의한다.)
- 여러 자식 클래스가 필요로 하지 않는 멤버 함수가 여럿이 있는 경우 각 클래스가 해당 멤버 함수를 빈 함수로 재 정의하기 보다는 해당 멤버 함수들을 모아 재 정의한 중간 클래스를 정의하여 사용한다.
C++ 언어에서 클래스 간의 상속을 표현하는 구문은 다음과 같다.
clss <자식클래스 이름> : <접근 지정자> <부모 클래스 이름>
{
// 자식 클래스에서 추가할 멤버 변수와 함수
};
클래스를 상속할 경우 자식 클래스는 부모 클래스의 모든 멤버 변수와 멤버 함수를 상속받는다. 자식 클래스는 부모 클래스의 멤버 변수와 멤버 함수를 마치 자신의 멤버 변수와 멤버 함수인 것 처럼 사용할 수 있다.
C++ 프로그램에서 상속을 사용하면 같은 코드를 중복해서 작성할 필요가 없다. 자동차의 경우 모든 자동차가 가속 페달을 밟으면 속도가 올라가고 브레이크 페달을 밟으면 속도를 줄이고 결국에 멈추게 된다.
이러한 기능을 일반적인 자동차
클래스를 정의할 때 구현하게되면,
자동차
클래스를 상속받는 버스
, 화물차
, 승용차
등의 클래스는 이 기능을 각각 구현할 필요 없이 자동차
클래스를 정의할 때
구현한 기능을 사용할 수 있다.
그리고 여러 자식 클래스에서 공통으로 사용되는 기능이 하나의 부모 클래스에서 구현되어 있기 때문에 기능을 수정 보완할 때도 부모 클래스의 멤버 함수 만을 수정 보완하면 되기 떄문에 유지 보수 관리가 쉬워진다.