이펙티브 자바책에 상속보다는 컴포지션을 사용해라 라는 주제가 있다.
이 주제를 보며 어? 그런가? 라는 생각이 들어 정리해 보았다. ㅋㅋㅋ,,,
상속?
상속은 is-a 관계를 나타낸다. 예를 들면, 자동차가 차량 클래스로부터 상속받는 경우, 자동차는 차량이라고 할 수 있다.
장점
- 코드 재사용: 부모 클래스의 기능을 자식 클래스가 상속받기 때문에, 중복된 코드를 작성할 필요가 없어 코드 재사용성이 높아진다.
- 확장성: 자식 클래스는 부모 클래스의 특성을 확장하거나 변경할 수 있다. 새로운 메서드를 추가하거나 기존 메서드를 재정의하여 새로운 동작을 추가할 수 있다.
- 폴리모피즘(Polymorphism) 구현: 상속을 통해 여러 클래스가 동일한 인터페이스를 공유하고, 이를 통해 다형성을 구현할 수 있다.(객체지향 프로그래밍의 강력한 기능 중 하나)
- 결합도 증가: 자식 클래스는 부모 클래스에 의존하므로, 부모 클래스의 변경이 자식 클래스에 영향을 미칠 수 있다. 이는 결합도를 높이고 유지보수를 어렵게 만들 수 있다.
- 복잡성 증가: 상속 계층이 깊어질수록 코드의 복잡성이 증가할 수 있으며, 클래스 간의 관계를 이해하기 어려워질 수 있다.
- 제한된 확장성: 이미 다른 클래스를 상속받은 경우, 더 이상 다른 클래스의 상속받을 수 없으므로 확장성이 제한될 수 있다. ([[Item 19]])
- 강한 결합도: 상속은 부모 클래스와 자식 클래스 간에 강한 결합을 만들 수 있으므로, 이로 인해 재사용성과 유지보수성이 저하될 수 있다.
컴포지션?
객체지향 프로그래밍에서 한 클래스가 다른 클래스의 인스턴스를 포함하고, 이를 활용하여 원하는 기능을 구현하는 개념이다. 즉, 컴포지션은 객체들을 조합하여 더 복잡한 동작을 수행하기 위한 방법 중 하나이다.
컴포지션은 주로 has-a 관계를 나타낸다. 예를 들어, 자동차 클래스가 엔진 클래스의 인스턴스를 포함하는 경우, 자동차 "has-an" 엔진이다. 이를 코드로 나타내면
public class Engine {
// 엔진 관련 기능들
}
public class Car {
private Engine engine; // 자동차는 엔진을 포함
public Car() {
engine = new Engine(); // 컴포지션을 통해 엔진을 생성 및 포함
}
// 자동차와 관련된 기능들
}
위의 예에서 자동차 클래스는 엔진 클래스의 인스턴스를 포함하고 있으며, 이를 통해 자동차의 동작을 구현한다. 이것이 컴포지션의 핵심이다.
장점
- 유연성: 컴포지션을 사용하면 더 작은 단위로 객체를 조립할 수 있으므로, 필요한 동작을 더 쉽게 변경하거나 확장할 수 있다. 이는 객체들 간의 느슨한 결합을 유지하며 변경이 쉬운 코드를 작성하는 데 도움이 된다.
- 코드 재사용: 컴포지션을 통해 더 작은 컴포넌트를 재사용하여 새로운 기능을 구현할 수 있다. 기존의 클래스를 상속받는 것보다 더 모듈화 된 컴포넌트를 활용할 수 있다.
- 결합도 감소: 컴포지션은 클래스 간의 결합도를 낮추고, 하나의 클래스가 다른 클래스의 내부 구현에 의존하지 않도록 한다. 이로써 변경 시 다른 클래스에 미치는 영향을 최소화할 수 있다.
그럼 각각 언제 사용하는 게 좋을까?
상속을 사용하는 경우
- is-a 관계: 클래스 A가 클래스 B의 특수한 경우로 설명될 때 상속을 고려한다. 예를 들어, "사람은 동물이다"라고 말할 수 있으며, 이 경우 "Person"클래스가 "Animal"클래스를 상속받는 것은 합리적이다.
- 부모 클래스의 기능 확장: 부모 클래스의 기능을 확장하고자 할 대 상속이 유용하다. 자식 클래스는 부모 클래스의 메서드를 재정의하여 새로운 동작을 추가하거나 변경할 수 있다.
- 다형성 활용: 다형성을 이용하여 여러 클래스를 동일한 인터페이스로 사용하려면, 상속을 통해 공통된 부모 클래스를 만들 수 있다.
컴포지션을 사용하는 경우
- has-a 관계: 클래스 A가 클래스 B를 포함하고 있지만 클래스 B가 클래스 A의 일부가 아닌 경우 컴포지션을 고려한다. 예를 들어, "자동차는 엔진을 가지고 있다"라고 말할 때, "Car"클래스가 "Engine"클래스의 인스턴스를 포함하면 컴포지션을 사용한다.
- 유연한 설계: 유연성과 느슨한 결합을 원할 때 컴포지션을 사용한다.
- 다중 상속 회피: 다중 상속 문제를 피하려면 컴포지션을 활용할 수 있다.
- 인터페이스 분리 원칙: 컴포지션은 인터페이스 분리 원칙(ISP)을 지키기 쉽게 만든다. 필요한 기능을 가진 작은 컴포넌트를 조합하여 인터페이스를 더 작고 명확하게 유지할 수 있다.
'Java' 카테고리의 다른 글
if-else vs try-catch: 언제 어떤 것을 사용해야 할까? (0) | 2023.09.25 |
---|---|
[Java] Java 8에서 함수형 프로그래밍이 도입된 이유 (0) | 2023.07.18 |
[Java] copyOf(), copyOfRange(), arrayCopy() (0) | 2023.06.29 |
객체 지향의 5가지 원칙 (1) | 2023.01.27 |
[Java] 어노테이션(Annotation) (1) | 2022.11.24 |