[소프트웨어공학] 클래스 간의 관계 1. Generalization (Inheritance)
저번 포스팅에 이어 오늘은 Inheritance에 대한 포스팅을 해보겠습니다.
https://cuckoobird.tistory.com/211
[소프트웨어공학] 클래스 간의 관계
이번 학기에 듣게 된 '시스템분석설계' 라는 과목에서 배운 내용을 정리해보고자 글을 씁니다.각 관계에 대한 자세한 설명은 따로 포스팅하여 예제 코드와 함께 링크 걸겠습니다. 클래스 관계
cuckoobird.tistory.com
Generalization (Inheritance) 관계
Generalization 관계는 두 가지의 특징을 가집니다.
1. 부모와 자식 관계: 상위 클래스와 하위 클래 관계가 부모 클래스, 자식 클래스로 작용합니다. 이를 두고 상속(Inheritance) 관계 라고 합니다.
2. 확장 관계: 자식 클래스는 부모 클래스에게 상속받은 필드나 메서드 등을 가질 수 있으므로 부모클래스를 포함하고 있으며, 이는 자식 클래스가 부모 클래스를 확장(extends)한다고 합니다.
Inheritance 관계를 구현하는 방법
1. 클래스에서 상속 구조 만들기
N개의 클래스가 있다고 했을 때, N개의 클래스에 들어갈 속성과 메서드에서 공통적인 것들을 고릅니다. 만약 Car와 MotorBike 클래스가 있다고 했을 때를 가정해봅시다.
두 클래스의 공통 속성과 메서드는 위와 같이 나타날 수 있을 것입니다.
2. 공통 속성과 메서드로 상위 클래스를 만든다
Car와 MotorBike의 공통 속성과 메서드는 model, color, wheels, moveFoward(), moveBackward()가 있으니 이들을 상위클래스, 즉, 부모클래스에 넣는다.
상속 사용 전 코드
코드가 다음과 같이 공통 속성과 메서드가 두 클래스에 중복으로 있다고 한다면 상속을 사용해봅시다.
상속 사용 후 코드
부모 클래스인 Vehicle이 Car와 MotorBike 자식 클래스를 갖도록 위와 같이 코드를 짜보았습니다.
- Vehicle은 Car와 MotorBike의 공통 속성과 메서드를 갖고 있습니다.
- Car와 MotorBike는 Vehicle을 상속 받으므로(extends) Vehicle에 있는 속성과 메서드를 중복하여 쓰지 않아도 됩니다.
- 만약 메서드를 부모 클래스의 내용과 다르게 사용하고 싶다면 @Override를 이용하여 재정의를 합니다.
상속의 장점
- 이해 용이
- 코드의 재사용성, 확장성
- 유지보수 용이: 필드, 메서드를 변경 시 상위 클래스만 변경해도 됩니다.
- 추상화 가능: 일반화/특수화 관계를 통해 추상화 단계를 표현 가능합니다.
상속의 문제점
좋은 설계란, 낮은 결합도(coupling)과 높은 응집도(cohesion)을 요구하고, 이는 추상화를 통해 가능합니다. 이런 점에서 상속의 문제점을 찾아볼 수 있습니다.
1. 상위 클래스의 캡슐화가 깨지고 결합도가 높아짐
자식 클래스가 부모 클래스의 logic에 대해서 알고있어야 사용이 가능합니다. 이는 불필요한 구현 내용노출을 일으켜 캡슐화를 깨뜨립니다.
만약 Car 클래스를 상속받는 SportsCar 클래스가 있다고 했을 경우, Car 클래스의 hasEnoughFuel() 메서드에서는 fuel 이 20이상이어야 연료가 충분하다고 판단하여 drive() 메서드를 동작 하는데, SportsCar에서는 이를 모르고 연료를 10으로 무작정 Car 클래스의 drive() 메서드를 동작시킬 경우에 SportsCar의 drive()메서드가 연료부족으로 동작하지 않게 될 것입니다.
2. 유연성 및 확장성이 떨어짐
부모 클래스에 필드를 추가하게 되어 생성자에 들어가는 파라미터가 늘어났을 경우에, 자식 클래스의 생성자에도 똑같이 파라미터를 늘려야 합니다. 이는 확장성을 저해하 유지보수의 복잡성을 증가시킵니다.
부모 클래스의 메서드 이름을 변경할 경우에 자식 클래스에서 Override한 메서드의 이름도 변경해야한다는 것도 이에 속합니다.
변경에 대한 범위가 상당히 커지는 것은 유연성과 확장성을 떨어뜨립니다.
3. 클래스 폭발 문제가 발생할 수 있음
클래스 폭발은 하위 클래스가 상위 클래스의 구현과 강하게 결합되도록 강요하는 상속의 근본적인 한계 때문에 발생합니다.
Compile time에 부모 클래스와 자식 클래스 간의 관계를 변경할 수 없기 때문에 두 클래스 간의 다양한 조합이 필요한 상황에는 조합의 수 만큼 새로운 클래스를 추가하는 것 밖에 방법이 없습니다.
또한 클래스 폭발 문제는 새로운 기능을 추가하는 것 뿐만 아닌 수정하는 데에 있어서도 동일하게 발생합니다.
상속의 사용 이유
- 타입 계층의 구현
- 코드의 재사용
하지만 재사용을 위해 상속을 사용하는 것은 제약이 많기 때문에 사용하지 않는 것이 좋습니다.
- 상위-하위 클래스가 is-a 관계인 경우
'타입 A는 B이다' 라고 표현할 수 있을 때 상속을 사용합니다. 예를 들어, '펭귄은 새이다.' 라는 것은 전혀 이상하지 않죠? 새와 펭귄의 관계를 구현하고자 할 때 상속을 사용할 수 있습니다. 하지만 여기 더 검토해야 할 조건이 있습니다.
- 행동 호환성이 만족하는 경우
클라이언트 입장에서 보았을 때, 상위 클래스와 하위 클래스의 차이점을 느끼지 못해야 하며, 상위 클래스 타입으로 하위 클래스를 사용해도 문제가 없어야 합니다.
두 타입이 동일하게 행동할 것이라고 기대한다면 두 타입을 묶을 수 있습니다.
만약 그렇지 않다면 두 타입을 하나의 타입계층으로 묶어서는 안 됩니다.
만약 '새가 날 수 있다'라는 fly() 메서드와 '펭귄은 날 수 없다' 라는 기능이 충돌한다면,
1. fly() 메서드를 비워둔다
2. fly() 메서드 호출 시 예외처리를 한다
3. instance of 로 타입을 검사하여 펭귄이 아닌 경우에만 fly() 메서드를 호출한다
의 방법이 있습니다.
만약 이를 해결하지 않는다면, 펭귄 객체가 fly()메서드를 이해할 수 있게 되어 문제가 발생하게 됩니다.
행동 호환성
is-a 관계라도 행동 호환성을 검사하는 과정이 필요합니다. 위와 같이 상속을 사용하는 경우에는 제한적인 경우가 많습니다. 이를 상속 관계로 해결하기 보다는 interface를 이용하여 해결하는 방법이 적절합니다.
짤막한 회고
상속을 사용만 할 줄 알지 어떻게 써야하는지 왜 써야하는지에 대해서 몰랐습니다. 그저 가벼운 마음으로 포스팅을 해보았는데 생각보다 상속 키워드 하나만 가지고 포스팅을 하려 공부하다보니 이에 깊은 이론들이 있어서 놀랐습니다.
객체 지향이란 깊이 들어갈수록 어렵지만 흥미있는 주제인 것 같습니다. 객체지향의 특징 중 상속이라는 것은 inheritance가 아닌 interface의 느낌이 강한 것 같네요.
뭔가 오늘부터 저번 시스템분석실습 강의에 배웠던 내용들을 하루에 하나씩 포스팅하게 될 것 같은데 본의아닌(?) 명절 특집 정리 챌린지를 시작하게 된 것 같은 기분이에요. ㅋㅋ
다른 일들도 병행해야지만.. ㅋㅋ 열심히 해보겠습니다!