JAVA

[JAVA] 코드의 재사용을 상속(Inheritance)으로 모두 해결할 수 있을까?

sagecode 2025. 6. 11. 18:52

프로젝트를 하다 보면, 반복되는 기능을 여러 클래스에서 재사용하고 싶을 때가 있다. 이럴 때 상속을 이용하면 재사용 가능하다.

 

상속(Inheritance)이란?

상속(Inheritance)은 객체지향 프로그래밍의 개념 중 하나로, 기존 클래스의 기능을 자식 클래스가 물려받는 것을 말한다. 중복 코드를 줄이고, 공통 기능을 재사용하기 위한 수단이다.

public class Animal {
    public void breathe() {
        System.out.println("숨을 쉰다");
    }
}

public class Dog extends Animal {
    public void bark() {
        System.out.println("멍멍");
    }
}

이렇게 자식 클래스인 Dog이 부모 클래스인 Animal의 breathe()도 사용가능하다.

 

하지만 점점 복잡한 애플리케이션을 만들다 보면, 상속만으로는 해결할 수 없는 문제들이 발생합니다.

  • 부모 메소드의 몇몇 기능이 필요없지만 상속이 필요할 때
  • 부모 클래스의 변경이 자식 클래스에 영향을 줄 때
  • 다중 상속이 불가능하지만 해야 할 때

이러한 경우에 상속(Inheritance)이 아닌 다른 방법을 고민해야 한다. 컴포지션(Composition)이라는 개념을 활용하면 된다.

컴포지션(Composition)이란?

컴포지션(Composition) 은 기존 클래스의 기능을 "상속받지 않고 포함(위임)"하는 방식으로 재사용하는 설계 방법이다. 즉, 하나의 클래스가 다른 클래스를 필드로 포함하고, 그 기능을 호출하여 사용하는 구조이다.

 

이 방식은 "has-a" 관계를 표현하며, 유연한 코드 구조와 낮은 결합도를 제공한다.

 

구현

// 엔진 클래스
public class Engine {
    public void start() {
        System.out.println("엔진이 켜졌습니다.");
    }
}

// 자동차 클래스 (Engine을 갖고있음)
public class Car {
    private Engine engine = new Engine(); // Composition

    public void drive() {
        engine.start(); // 엔진의 기능을 위임
        System.out.println("자동차가 출발합니다.");
    }
}


public class Main {
    public static void main(String[] args) {
        Car car = new Car();
        car.drive();
    }
}
  • Car는 Engine을 상속받지 않고 필드로 포함한다.
  • 내부 구현을 Engine에게 위임함으로써 상속과는 다르게 역할이 구분 되어진다.

상속과 컴포지션의 장단점

 

구분 상속(Inheritance) 컴포지션(Composition)
개념 부모 클래스의 기능을 자식 클래스가 물려받아 재사용 다른 클래스의 기능을 내부에 포함하고 위임
관계 표현 "is-a" 관계 (ex. Dog is an Animal) "has-a" 관계 (ex. Car has an Engine)
장점 - 코드 재사용이 쉽다
- 계층 구조가 명확하다
- 다형성(polymorphism)을 활용 가능
- 더 유연하고 변경에 강하다
- 필요 기능만 조합 가능
- 런타임에 객체를 바꿀 수 있다
단점 - 부모 클래스에 강하게 결합됨
- 부모 변경 시 자식도 영향 받음
- Java는 다중 상속이 불가능
- 구현이 조금 더 복잡할 수 있음
- 위임 메소드가 많아질 수 있음
유지보수 변경에 약하다 (결합도 높음) 변경에 강하다 (결합도 낮음)
테스트 용이성 테스트 어려울 수 있음 테스트 용이 (구성 요소 별 테스트 가능)

 

상황별 사용

 

  • 상속이 유리한 경우
    • 여러 클래스가 동일한 동작과 구조를 공유할 때
    • "is-a" 관계가 자연스러운 경우
  • 컴포지션이 유리한 경우
    • 기능 조합이 유연해야 할 때
    • 변경과 확장이 잦은 애플리케이션
    • 테스트, 유지보수가 중요한 프로젝트