합성 vs 상속
Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 104개 중 76번째.
클래스 간의 관계를 설계할 때, 두 가지 주요 선택지가 있습니다: 상속 ("is-a")과 합성 ("has-a"). 올바른 접근 방식을 선택하는 것은 코드의 유연성과 유지보수성에 큰 영향을 미칩니다.
상속은 클래스 간의 강한 결합을 만듭니다. Vehicle을 상속받는 Car는 그 관계에 영구적으로 종속됩니다:
class Vehicle {
public:
virtual void start() { std::cout << "Starting vehicle\n"; }
};
class Car : public Vehicle {
public:
void start() override { std::cout << "Starting car\n"; }
};합성(Composition)은 객체를 멤버로 포함하여 더 유연한 관계를 만듭니다. Car는 엔진 그 자체가 되는 것이 아니라 Engine을 가집니다:
class Engine {
public:
void start() { std::cout << "Engine running\n"; }
};
class Car {
private:
Engine engine; // Car는 Engine을 가집니다
public:
void start() { engine.start(); }
};컴포지션(composition)의 핵심 장점은 유연성입니다. 엄격한 클래스 계층 구조의 제약 없이 구성 요소를 쉽게 교체하거나, 런타임에 동작을 변경하거나, 여러 기능을 결합할 수 있습니다. 현대적인 C++ 설계는 대부분의 경우 상속보다 컴포지션을 선호합니다.
진정한 "is-a" 관계가 있고 다형성이 필요할 때는 상속을 사용하세요. 기능을 재사용하고 싶거나 관계가 "has-a" 또는 "uses-a"로 더 잘 설명될 때는 합성을 사용하세요.
챌린지
쉬움상속 대신 구성을(composition) 언제 사용해야 하는지 보여주는 컴퓨터 시스템 시뮬레이터를 만들어 보겠습니다. 컴퓨터가 구성 요소의 일종이 되는(inheritance) 것이 아니라, 구성 요소를 가지는(composition) 모델을 만들어 봄으로써, 구성이 런타임에 부품을 교체할 수 있는 유연성을 어떻게 제공하는지 배우게 됩니다.
코드는 다음 세 개의 파일로 구성됩니다:
Components.h: 컴퓨터가 포함할 수 있는 개별 구성 요소들을 정의합니다.std::string model과int cores를 private 멤버로 가지는CPU클래스를 만드세요. 두 값을 모두 받는 생성자,CPU [model] processing with [cores] cores를 출력하는process()메서드, 그리고getModel()메서드를 제공하세요.int sizeGB를 private 멤버로 가지는Memory클래스를 만드세요. 생성자,Loading data into [sizeGB]GB RAM을 출력하는load()메서드, 그리고getSize()메서드를 제공하세요.std::string type("SSD" 또는 "HDD" 등)과int capacityGB를 private 멤버로 가지는Storage클래스를 만드세요. 생성자,Reading from [capacityGB]GB [type]를 출력하는read()메서드, 그리고 두 필드에 대한 getter 메서드를 제공하세요.Computer.h: 구성을 사용하여 구성 요소들을 결합하는Computer클래스를 정의합니다.Computer클래스는CPU,Memory,Storage객체를 private 멤버로 가져야 합니다. 생성자 초기화 리스트를 사용하여 매개변수로부터 이러한 구성 요소들을 초기화하세요.다음 메서드들을 구현하세요:
boot()—Booting computer...를 출력한 다음, 메모리의load(), 스토리지의read(), CPU의process()를 각각 별도의 줄에 호출합니다.specs()— 컴퓨터의 사양을 다음 형식으로 출력합니다:Computer Specs: - CPU: [model] ([cores] cores) - RAM: [size]GB - Storage: [capacity]GB [type]
main.cpp: 여섯 개의 입력을 읽습니다 (각각 별도의 줄):- CPU 모델 이름 (문자열)
- CPU 코어 수 (정수)
- 메모리 용량 GB (정수)
- 스토리지 유형 (문자열, 예: "SSD")
- 스토리지 용량 GB (정수)
- 명령어: "boot" 또는 "specs" 중 하나
구성 요소 객체들을 생성한 다음, 구성을 사용하여
Computer를 생성하세요. 명령어에 따라boot()또는specs()를 호출합니다.
예를 들어, 입력값이 Intel i7, 8, 16, SSD, 512, boot인 경우:
Booting computer...
Loading data into 16GB RAM
Reading from 512GB SSD
CPU Intel i7 processing with 8 cores입력값이 AMD Ryzen, 6, 32, HDD, 1000, specs인 경우:
Computer Specs:
- CPU: AMD Ryzen (6 cores)
- RAM: 32GB
- Storage: 1000GB HDDComputer 클래스가 어떤 구성 요소로부터도 상속받지 않고, 그것들을 포함하고 있다는 점에 주목하세요. 이러한 "has-a" 관계는 다양한 구성 요소 조합으로 컴퓨터를 쉽게 만들 수 있게 하며, 나중에 setter 메서드를 추가한다면 구성 요소를 교체할 수도 있음을 의미합니다. 이러한 유연성이 상속보다 구성이 갖는 핵심적인 장점입니다.
치트 시트
클래스 간의 관계를 설계할 때, 상속("is-a")과 합성("has-a") 중에서 선택하세요.
상속은 클래스 간의 강한 결합을 생성합니다:
class Vehicle {
public:
virtual void start() { std::cout << "Starting vehicle\n"; }
};
class Car : public Vehicle {
public:
void start() override { std::cout << "Starting car\n"; }
};합성은 객체를 멤버로 포함하여 유연한 관계를 만듭니다:
class Engine {
public:
void start() { std::cout << "Engine running\n"; }
};
class Car {
private:
Engine engine; // Car는 Engine을 가짐
public:
void start() { engine.start(); }
};각각을 사용하는 경우:
- 다형성이 필요한 진정한 "is-a" 관계에는 상속을 사용하세요
- 기능을 재사용하거나 "has-a"/"uses-a" 관계에는 합성을 사용하세요
- 현대적인 C++ 설계는 유연성을 위해 합성을 선호합니다. 컴포넌트를 교체하고, 런타임에 동작을 변경하며, 엄격한 계층 구조 없이 기능을 결합할 수 있기 때문입니다
직접 해보기
#include <iostream>
#include <string>
#include "Computer.h"
using namespace std;
int main() {
// 입력 읽기
string cpuModel;
getline(cin, cpuModel); // CPU 모델명 (공백을 포함할 수 있음)
int cpuCores;
cin >> cpuCores; // CPU 코어 수
int memorySize;
cin >> memorySize; // GB 단위의 메모리 크기
string storageType;
cin >> storageType; // 저장 장치 유형 (예: "SSD")
int storageCapacity;
cin >> storageCapacity; // GB 단위의 저장 용량
string command;
cin >> command; // 명령어: "boot" 또는 "specs"
// TODO: 구성 요소 객체 생성 (CPU, Memory, Storage)
// TODO: 구성을 사용하여 Computer 객체 생성
// TODO: 명령어에 따라 boot() 또는 specs() 호출
return 0;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.