컴포지션 vs 상속
Coddy Java 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 87개 중 47번째.
상속은 "is-a" 관계를 생성합니다: Dog는 Animal입니다. 하지만 클래스가 자신이 아닌 다른 것으로부터 기능이 필요하다면 어떻게 될까요? 여기서 합성(composition)이 등장하며, 대신 "has-a" 관계를 설정합니다.
컴포지션을 사용하면 클래스는 다른 클래스를 상속하는 대신 해당 클래스의 인스턴스를 필드로 포함합니다:
// 상속 방식: Car는 Engine입니다 (말이 되지 않음)
class Car extends Engine { }
// 구성(Composition) 방식: Car는 Engine을 가지고 있습니다 (말이 됨)
class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.ignite();
}
}컴포지션(Composition)은 여러 가지 장점을 제공합니다. 엔진을 교체하는 것과 같이 런타임에 구성된 객체를 변경할 수 있습니다.
또한 여러 클래스의 동작을 결합할 수 있는데, 이는 Java의 상속에서는 허용되지 않는 기능입니다. 구성된 클래스는 선택한 메서드만 노출하므로 더 나은 캡슐화를 제공합니다.
실질적인 지침: 진정한 "is-a" 관계가 있고 서브클래스가 부모의 전문화된 버전을 진정으로 나타낼 때 상속을 사용하세요.
다른 클래스의 기능을 해당 타입이 되지 않고도 사용해야 할 때 합성을 사용하세요. 많은 숙련된 개발자들은 "상속보다는 합성을 선호하라"는 원칙을 따르는데, 이는 더 유연하고 유지보수가 쉬운 코드로 이어지기 때문입니다.
챌린지
쉬움"has-a" 관계를 보여주기 위해 컴포지션(composition)을 사용하여 컴퓨터 시스템을 구축해 봅시다. Computer가 구성 요소로부터 상속받는 대신(이는 논리적으로 맞지 않습니다), 컴퓨터가 작동하는 데 필요한 부품들을 포함(contain)하도록 만들 것입니다.
코드를 세 개의 파일로 나누어 구성합니다:
Processor.java: CPU를 나타내는 클래스를 생성합니다. Processor는 두 개의 private 필드인brand(String)와speedGHz(double)를 가집니다. 두 필드를 초기화하는 생성자와 각 필드에 대한 getter 메서드를 포함하세요.[brand] processing at [speedGHz] GHz라는 문자열을 반환하는process()메서드를 추가하세요.Memory.java: RAM을 나타내는 클래스를 생성합니다. Memory는 두 개의 private 필드인type(String, 예: "DDR4" 또는 "DDR5")와sizeGB(int)를 가집니다. 생성자, getter 메서드, 그리고Loading [sizeGB]GB [type] memory를 반환하는load()메서드를 포함하세요.Computer.java: 여기서 컴포지션의 진가가 발휘됩니다! Computer 클래스는 Processor와 Memory를 private 필드로 가져야(have) 합니다. 즉, 상속(extend)받는 것이 아니라 포함하는 것입니다. 두 구성 요소를 매개변수로 받는 생성자를 포함하세요. 구성 요소들을 사용하여 컴퓨터가 시작되는 모습을 보여주는 다중 행 문자열을 반환하는boot()메서드를 추가하세요:또한Booting computer... [processor.process() result] [memory.load() result] System ready!Specs: [processor brand] CPU, [memory sizeGB]GB [memory type]를 반환하는getSpecs()메서드를 추가하세요.Main.java: 구성된 시스템을 실행해 봅시다! 프로세서 브랜드(String), 프로세서 속도(double), 메모리 유형(String), 메모리 크기(int)의 네 가지 입력을 받게 됩니다.이 값들로 Processor와 Memory를 생성한 다음, 이를 Computer로 구성하세요.
boot()를 호출한 결과를 출력하고, 이어서getSpecs()를 호출한 결과를 출력하세요.
프로세서 브랜드, 프로세서 속도(GHz), 메모리 유형, 메모리 크기(GB) 순서대로 네 개의 입력을 받게 됩니다.
Computer가 스스로 모든 일을 하려 하기보다 구성 요소에 작업을 위임하는 방식에 주목하세요. 이것이 바로 컴포지션의 힘입니다! Computer는 제공된 어떤 Processor나 Memory도 사용할 수 있어 설계가 유연하고 현실적입니다.
치트 시트
컴포지션(Composition)은 클래스가 다른 클래스를 상속(extending)하는 대신 다른 클래스의 인스턴스를 필드로 포함하는 "has-a" 관계를 생성합니다.
상속(Inheritance) vs 컴포지션(Composition):
// 상속: Car IS an Engine (말이 되지 않음)
class Car extends Engine { }
// 컴포지션: Car HAS an Engine (말이 됨)
class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.ignite();
}
}컴포지션의 장점:
- 런타임에 객체를 변경할 수 있습니다
- 여러 클래스의 동작을 결합할 수 있습니다
- 더 나은 캡슐화 - 선택한 메서드만 노출합니다
- 더 유연하고 유지보수가 쉬운 코드
사용 시기:
- 상속: 진정한 "is-a" 관계가 있고 서브클래스가 부모 클래스의 특수화된 버전일 때 사용합니다
- 컴포지션: 해당 타입이 되지 않고도 다른 클래스의 기능이 필요할 때 사용합니다
권장 사항(Best Practice): "상속보다는 컴포지션을 지향하라"
직접 해보기
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 입력 읽기
String processorBrand = scanner.nextLine();
double processorSpeed = Double.parseDouble(scanner.nextLine());
String memoryType = scanner.nextLine();
int memorySize = Integer.parseInt(scanner.nextLine());
// TODO: 브랜드와 속도를 사용하여 Processor 생성
// TODO: 타입과 크기를 사용하여 Memory 생성
// TODO: Processor와 Memory를 조합하여 Computer 생성
// TODO: boot()의 결과 출력
// TODO: getSpecs()의 결과 출력
}
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
4상속
상속의 기초 (extends)super 키워드메서드 오버라이딩 (@Override)생성자 체이닝Object 클래스단일 및 다중 레벨 상속다중 클래스 상속이 불가능한 이유요약 - 직원 계층 구조