Java와 같은 객체 지향 프로그래밍 언어에서는 상속(Inheritance)이라는 중요한 개념이 존재한다. 상속과 조합을 비교하기 전, 먼저 상속에 대해 알아보자
Inheritance
상속이란 부모 클래스(Super Class)의 특성을 자식 클래스(Sub Class)가 물려받아 사용할 수 있도록 해주는 메커니즘이다. Java에서는 extends 키워드를 사용하여 자식 클래스가 부모 클래스를 상속할 수 있다. 상속의 특징을 통해 장단점과 사용 목적을 알아보자
Java의 상속 (Inheritance)
- 코드 재사용성
: 상속을 통해 동일한 속성을 가진 객체들이, 매번 동일한 필드와 메서드를 작성할 필요 없이 '부모 클래스'에 선언된 필드와 메서드를 그대로 사용하게 함으로써 코드 재사용성을 높여준다 - 확장 가능성
: '부모 클래스'의 필드나 메서드를 변경하지 않고서도 '자식 클래스'의 고유한 필드나 메서드를 선언하여 기능을 확장할 수 있다
단, 코드 재사용만을 위해서 상속을 사용한다 생각하면 안된다. 엄연히 상위 클래스를 하위 클래스를 통해 좀 더 구체적으로 구현하기 위해 사용되는게 상속이라는 것을 명심해야한다 - 메서드 오버라이딩
: 반대로 '자식 클래스'에서 '부모 클래스'의 메서드를 그대로 사용하지 않고 재정의 함으로써, '부모 클래스'의 메서드를 바꾸지 않고서도 '자식 클래스'에서 원하는대로 내부 동작을 변경할 수 있다 - 단일 상속
: 다중 상속을 지원하지 않으므로, '자식 클래스'는 단 하나의 '부모 클래스'만을 상속할 수 있다
이처럼 상속은 코드의 재사용성과 확정성을 높이고 프로그램의 구조를 계층화하여 유지보수성을 높이는 등의 장점이 있다. 하지만 적절하게 사용하지 않으면 클래스 간의 의존성이 높아져 유연성이 떨어지는 단점도 존재한다
상속의 단점
- 강한 결합성
: '부모 클래스'와 '자식 클래스'가 강하게 결합되기에, '자식 클래스'는 '부모 클래스'의 내부 구현에 의존하게 된다. 예를들어 '부모 클래스'의 메서드 시그니처가 변경된다면 '자식 클래스'에서의 변경 또한 필수가 된다 - 불필요한 상속
: 상속을 통해 '부모 클래스'의 모든 기능이 '자식 클래스'로 상속되므로, 불필요한 메서드나 필드도 상속될 수 있다 - 다중 상속 불가능
: '자식 클래스'가 하나의 '부모 클래스'를 상속하고 있다면, 추가적으로 상속할 수 없다. 여러 '부모 클래스'에서 동일한 메서드 시그니처를 가진다면 어떤 '부모 클래스'의 메서드를 상속받을지 알 수 없기 때문이다(인터페이스는 가능하다) - 유연하지 못한 설계
: 컴파일시에 구성 요소들이 정해지므로, 구성 요소를 동적으로 변경할 수 없다
// 인터페이스의 다중 상속
public interface Father{
void love();
}
public interface Mother{
void love();
}
public class Child implements Father, Mother {
// 어떤 인터페이스의 메서드를 상속받았는지 전혀 상관이 없다
@Override
void love(){
System.out.println("L.O.V.E");
}
public static void main(){
Child child = new Child();
child.love(); // "L.O.V.E"
}
}
Composition
그렇다면 위에서 말한 상속의 단점들을 어떻게 조합으로 해결할 수 있는지, 컴포지션의 특징을 통해 알아보자
Composition
- has - a 관계
: 클래스가 다른 객체의 인스턴스를 포함하는 개념으로, 특정 객체의 인스턴스(구현체)를 자신의 인스턴스 변수(필드)로 선언한 후, 그 인스턴스의 필드나 메서드 등을 사용하는 기법이다. - 수평적 구조
: 상속이 'is-a' 관계를 통해 수직적 구조를 가져간다면, 조합은 'has-a'관계를 통해 수평적 구조를 가져간다. 따라서 (Student, Subjcet), (Car, Engine)처럼 아주 연관이 없지는 않지만 상속 관계로 맺기에는 애매한 것들을 다룰 수 있다 - 낮은 결합도
: 포함한 인스턴스의 내부 구현이 바뀌더라도, 자신(인스턴스 변수를 선언한 클래스)은 영향을 받지 않는다 - 선택적 상속
: 포함한 인스턴스의 모든 메서드 및 필드를 상속받아 사용하는 것이 아니라, 내가 원하는 기능만을 사용할 수 있으므로 불필요한 기능을 상속하지 않아도 된다 - 유연한 설계
: 필드로 포함한 인스턴스가 interface의 구현체가 될 수도 있다. 따라서 자신의 인스턴스 변수로 interface를 선언했을 때, 그 interface를 구현한 구현체라면 어떤 객체라도 자신의 필드로 포함시킬 수 있다. 따라서 runtime시에 동적으로 객체의 타입을 변경할 수 있다 - 다중 상속
: 몇 개의 클래스가 됐든, 그 클래스들의 인스턴스를 필드로 포함시키기만 한다면 각각의 클래스들의 기능을 모두 사용할 수 있다
이처럼 반드시 어떤 특정한 관계일 때만 상속을 사용하고, 그 외의 경우에는 조합을 적극 사용하자
상속 예시
// 상속
public abstract class Developer {
private final String name;
public Developer(String name) {
this.name = name;
}
public void talk() {
System.out.println("I am " + name);
}
abstract void create();
}
public class Backend extends Developer {
public Backend(String name) {
super(name);
}
@Override
public void create() {
System.out.println("create api");
}
}
public class Frontend extends Developer {
public Frontend(String name) {
super(name);
}
@Override
public void create() {
System.out.println("create ux/ui");
}
}
조합 예시
public class Developer_composition {
private final String name;
private final Role role; // role 객체를 인스턴스 변수로 포함시킨다
public Developer_composition(String name, Role role) {
this.name = name;
this.role = role;
}
public void talk() {
System.out.println("I am " + name);
}
// role의 메서드를 그대로 사용한다
public void create() {
role.create();
}
}
public class BE_composition implements Role {
@Override
public void create() {
System.out.println("create api");
}
}
public class FE_composition implements Role {
@Override
public void create() {
System.out.println("create ui");
}
}
생성 방법 및 사용 결과
public static void main(String[] args) {
// inheritance
Backend backend = new Backend("Backend Developer");
Frontend frontend = new Frontend("Frontend Developer");
backend.talk();
backend.create();
frontend.talk();
frontend.create();
System.out.println("=====================================");
// composition
Developer_composition BE_composition = new Developer_composition("Backend Developer", new BE_composition());
Developer_composition FE_composition = new Developer_composition("Frontend Developer", new FE_composition());
BE_composition.talk();
BE_composition.create();
FE_composition.talk();
FE_composition.create();
}
Reference
- Java 컴포지션(Composition): https://gyoogle.dev/blog/computer-language/Java/Composition.html
- Java 상속과 컴포지션에 대해서: https://velog.io/@vino661/%EC%83%81%EC%86%8D%EA%B3%BC-%EC%BB%B4%ED%8F%AC%EC%A7%80%EC%85%98%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C
- Java 컴포지션이 뭘까?: https://engineerinsight.tistory.com/28
- 자바의 다중상속 (feat. 인터페이스와 추상클래스): https://junior-datalist.tistory.com/213