본문 바로가기
Java/Java Basic

[Java Basic] 16. 클래스의 상속관계와 포함관계

by Rosmary 2022. 7. 11.
728x90
반응형

 

 

 

 

 

객체지향에서 클래스는 특정 객체의 특성 및 행위를 정의한 일종의 정의서다. 필자의 앞선 클래스 관련 포스팅을 보았다면 알겠지만, 클래스를 사용하면 우리 주변의 물체를 코딩으로 쉽게 구현할 수 있다. 그런데, 모든 물체를 하나의 클래스로 일일이 구현하기에는 비효율적인 면이 분명 존재한다.

 

예를 들어보자. 요즘 영화관에서 "탑건"이 매우 인기를 끌고 있으니, 비행기를 객체로 클래스를 한 번 만들어보려 한다. 필자는 두 모델에 대한 비행기를 클래스로 정의하려 한다. 하나는 경비행기인 Cessna 150이고, 다른 하나는 영화에 등장하는 미 해군 주력 기종인 F/A-18 E/F 호넷이다.

 

왼쪽: Cessna 150 프롭기, 오른쪽: FA-18 E/F

 

세스나는 Cessna150이라는 이름으로, 호넷은 FA18EF라는 이름으로 각각의 클래스를 생성하고 일부 특성과 엔진 On_Off에 대한 매서드만 추가해보았다.

 

 

 

위의 두 클래스를 비교해보면, Cessna150과 FA18EF는 - 경비행기와 전투기라는 차이점이 있지만 - 둘 다 비행기이기 때문에 중복되는 특성과 행위가 매우 많다. 이 경우 유지보수와 관리 측면에서 문제가 될 수 있는 것이 몇 가지 있다.

 

-  다른 비행기 모델에 대한 클래스를 추가로 정의할 때, 중복되는 내용으로 인해 쓸데없이 코드가 길어진다.

-  비행기의 특성 중, 추가/삭제해야 할 내용이 발생하는 경우, 모든 모델 클래스에 대해 특성 변수를 추가/삭제해야한다.

 

따라서 Java는 동일하거나 유사한 특성을 가지는 클래스의 상위 클래스를 정의하고, 상위 클래스의 특성과 행위를 사용할 수 있는 기능을 제공한다. 그리고 상위 클래스와 상위 클래스를 참조하는 하위 클래스의 관계를 상속관계로 정의한다.

 

본격적으로 클래스의 상속에 대해 알아보자.

 

 

 

1. 클래스의 상속

 

먼저, 서두에 예시로 들었던 두 비행기의 공통 특성과 행위를 모아 Airplane이라는 클래스를 생성하자. 필자는 Cessna150 클래스의 이름만 변경해서 Airplane 클래스를 정의했다.

 

 

 

여기에 앞서 만들었던 Cessna150과 FA18EF 클래스를 추가하려한다. 단, 클래스 이름 뒤에 extends Airplane을 추가한다.

 

 

 

Cessna150과 FA18EF 클래스 뒤에 붙은 extends 예약어는, 해당 클래스가 참조할 상위 클래스를 지정할 때 사용한다. 따라서 위의 코드는 Cessna150과 FA18EF클래스가 Airplane 클래스를 상위 클래스 또는 부모 클래스로 지정한다는 의미다.

다른 말로는 Cessna150과 FA18EF는 Airplane 클래스의 하위 또는 자식 클래스라고도 한다.

 

하위 클래스는 상위 클래스의 멤버변수와 매서드를 동일하게 가질 수 있게 된다. 따라서, 아래와 같이 Cessna150과 FA18EF 인스턴스들도, Airplane 클래스의 특성과 매서드를 호출하고 조회하는 것이 가능하다.

 

하위 두 클래스는 멤버변수와 매서드가 아무것도 정의되어 있지 않다는 것을 다시 확인하자.

 

 

이제, Cessna150과 FA18EF 클래스에, 바퀴 - 랜딩기어라고 한다 - 수를 추가로 정의해보자. 상위 클래스를 지정하기 전에는 Cessna150과 FA18EF 클래스 내에 랜딩기어와 관련된 멤버 변수를 각기 추가해야했지만, 상위 클래스가 지정된 지금은 Airplane 내에만 특성을 추가해주면 된다.

 

상위 클래스에 추가한 특성을 하위 클래스에서도 호출 가능함을 확인할 수 있다.

 

 

이제, Cessna와 FA-18 사이 차이점을 한 번 찾아보자. 가장 눈에 띄는 것은 FA-18은 전투기라 무장(폭탄, 미사일) 특성이 포함되어야 하고, 빠르게 날아가야하기 때문에 공기 저항을 줄이기 위해 랜딩기어를 동체 내에 수납할 수 있어야 한다. 하지만 Cessna는 이러한 특성을 가지지 않는다. 따라서 두 클래스 사이 차이가 있는 특성은 Airplane에 정의를 할 수 없다. 정의하는 순간 Cessna도 미사일과 폭탄을 운반할 수 있는 괴상한 객체가 되어버리기 때문이다.

 

그렇기 때문에 필자는 FA18EF 클래스에만 무장과 랜딩기어 수납 관련 특성 및 매서드를 넣으려고 한다.

 

 

 

위의 특성과 매서드는 FA18EF에만 정의되어 있기 때문에, FA-18 인스턴스만이 이들을 호출할 수 있다.

 

FA18EF 인스턴스에서 FA18EF 클래스 정의 멤버변수 및 매서드 호출 결과

 

Cessna150 인스턴스는 FA18EF 멤버변수와 매서드 사용 시 에러가 발생.

 

 

여기까지의 내용이 이해가 되신 분들은 상속을 사용하지 않고 각 객체에 대해 클래스 정의를 진행해보자. 위의 예시를 필자가 코드로 작성하니 대략 80줄이 나오는데, 상속을 사용하지 않는 경우, 주석이나 공백을 최소한으로 줄인다고 해도 대략 200줄의 코드가 나오지 않을까 싶다. 이 과정을 통해 클래스 상속은 객체지향 프로그래밍에서 코드의 관리와 유지보수 효율을 획기적으로 끌어올릴 수 있음을 쉽게 이해할 수 있을 것이다.

 

그럼, 객체 사이 어떤 상관관계를 가질 때, 상속 관계를 설정하면 좋을지 궁금하신 분들이 있을 것이다. 위의 객체들의 상관관계를 문장으로 바꿔보자.

 

 - Cessna 150은 비행기다(O)

 - FA-18 E/F는 비행기다(O)

 

둘 다 비행기이기 때문에 비행기를 정의한 클래스인 Airplane을 상위 클래스로 지정이 가능하다. 

 

- Cessna 150은 전투기다(X)

- FA-18 E/F는 전투기다(O)

- 전투기는 비행기다(O)

 

만약 전투기에 대한 클래스가 하나 더 생성된다면, 전투기와 Cessna150 클래스는 Airplane 클래스의 하위클래스가 될 수 있다. 하지만, Cessna150클래스는 전투기가 아니기 때문에 전투기 클래스를 상위 클래스로 지정할 수 없다(논리적으로 지정할 수 없다는 것이다. 코드로는 언제든지 가능하다. 단지 이 경우 Cessna에서 폭탄이 투하되기 때문에 문제지...).

 

 

 

즉, 클래스 상속 관계 설정 시, "A는 B다" 논리가 참이되면 B는 A의 상위 클래스가 될 수 있다.

 

 

 

2. 클래스 포함 관계

 

이제 필자는 Cessna150 클래스를 삭제하고 FA-18 E/F 전투기에 조금 더 집중해보려 한다. FA-18 전투기는 추력을 위한 제트 엔진을 2개 가지고 있다. 하지만 FA18EF 클래스에 정의된 engine_Status 인스턴스 변수와 change_engine_status 매서드는 실행 시, 두 엔진의 상태를 각기 표시하거나 변화시키는 것이 불가능하다. 이를 각기 표현할 방법이 있을까? 

 

먼저, 필자는 FA-18 E/F의 엔진에 대해서도 클래스로 정의를 하려 한다. 간단하게, 엔진의 상태, 모델과 상태 변화 매서드만 추가해보려 한다. 또한 기존 Airplane와 FA18EF 클래스에 정의된 엔진 상태와 상태 변화 매서드는 삭제한다.

 

 

 

기존의 Airplane과 FA18EF클래스에서 엔진관련 멤버변수와 매서드가 사라졌기 때문에, FA18EF은 엔진과 관련된 내용을 조회하거나 상태 변경을 진행할 수 없게 된다. 그렇기 때문에, 엔진 클래스의 인스턴스를 FA18EF 클래스 내에 생성해주어야 한다. FA-18 E/F는 엔진이 두 개이므로 GE_Turbofan 변수를 두 개 생성해주면 된다.

 

배열 형태가 아니라 일반 참조형 변수 2개를 만드는 것도 가능하다.

 

 

엔진과 관련된 변수는 참조형이므로, main 함수에서 인스턴스를 통해 호출할 경우, 아래와 같이 진행하면 된다.

 

 

두 엔진에 대해 각기 호출이 가능하다는 것은, 두 엔진을 끄고 켜는 매서드 역시 각기 적용이 가능하다는 이야기다. 엔진 1번 시동을 걸고 상태를 다시 보자.

 

 

 

이제 전체적으로 코드를 다시 한 번 살펴보자. 분명 FA18EF나 상위 클래스인 Airplane에는 엔진과 관련된 멤버변수나 매서드가 존재하지 않음에도, FA18EF 내부에 포함된 GE_Turbofan 클래스로 인해 각 엔진의 상태 조회와 컨트롤이 가능하다. 이렇게 하나의 클래스 내부에 다른 클래스가 포함될 경우, 두 클래스는 포함 관계를 가진다고 말한다.

 

포함관계는 다음과 같은 상황에서 매우 유용하다. 예를 들어, 이 GE사의 엔진이 FA-18 E/F 뿐만 아니라 국산 개발 전투기인 KF-21에 1기 장착된다고 하면, 별도로 엔진 관련 클래스를 정의하지 않고 코드를 재활용할 수 있게 된다. 

 

 

 

클래스 사이 포함관계는 어떠한 논리를 가질 때 사용이 가능할까? 보통 A has B의 구조를 이룰 때 포함관계를 사용할 수 있다.

 

-  FA-18 E/F는 GE_Turbofan 엔진을 장착한(가진)다(O)

-  KF21은 GE_Turbofan 엔진을 장착한(가진)다(O)

-  Cessna150은 GE_Turbofan 엔진을 장착한(가진)다(X)

 

물론 상속 관계와 마찬가지로 논리적으로 지정할 수 없다는 것이다. Cessna150 클래스 내에 GE_Turbofan을 포함관계로 설정할 수 있지만... 저 연약하디 연약한 비행기가 제트 엔진을 다는 순간 날아다니다 분해될 것이 뻔하니...

 

 


 

상속과 포함관계가 없는 객체지향 프로그램을 진행하다보면 각 객체에 대한 수많은 특성과 행위를 구현하는데 어려움이 많다. 앞서 보았던 코드 중복으로 인해 프로그램이 무거워지는 것은 물론이고, 이로 인한 수정 및 유지보수의 어려움도 발생하게 된다. 상속과 포함관계를 적절히 사용하는 경우 이러한 문제점이 획기적으로 감소한다. 따라서 절차형 프로그래밍과 달리, Java 프로그래밍은 코딩을 진행하기 전 객체간의 상관관계, 특성, 행위에 대한 철저한 분석이 가장 우선시되어야한다. 

 

다음 포스팅에서는 상속관계의 클래스에서 동일한 이름의 매서드를 다루는 오버라이딩(Overriding)에 대해 알아보려 한다.

 

 

 

 

Fin.

반응형

댓글