Menu
Coddy logo textTech

컴포지션

Coddy Rust 여정의 Object Oriented Programming 섹션에 포함된 레슨 — 61개 중 55번째.

많은 객체 지향 언어들은 타입 간의 기능을 공유하기 위해 상속에 크게 의존합니다. Rust는 다른 접근 방식을 취합니다: 구성(composition). 부모로부터 동작을 상속받는 대신, 다른 타입들을 필드로 포함함으로써 복잡한 타입을 구축합니다.

자동차를 모델링한다고 가정해 봅시다. 기본 Vehicle 클래스를 만들고 이를 상속받는 대신, 더 작고 집중된 컴포넌트들로부터 Car를 구성합니다:

struct Engine {
    horsepower: u32,
}

struct Wheels {
    count: u8,
    diameter: f32,
}

struct Car {
    engine: Engine,
    wheels: Wheels,
    brand: String,
}

CarEngine이나 Wheels에서 상속받지 않고, 그것들을 가지고 있습니다. 이러한 "has-a" 관계를 합성(composition)이라고 합니다. 필드를 통해 내부 컴포넌트에 접근하고 해당 메서드를 직접 호출할 수 있습니다:

impl Engine {
    fn start(&self) {
        println!("Engine with {} HP started!", self.horsepower);
    }
}

impl Car {
    fn start(&self) {
        self.engine.start();  // 내부 컴포넌트로 위임
    }
}

이 패턴은 각 구조체가 단일 책임에 집중하도록 유지합니다. 엔진이 작동하는 방식을 변경해야 합니까? Engine 구조체만 수정하면 됩니다. Wheels를 오토바이에 재사용하고 싶으신가요? 그냥 새로운 구조체에 포함시키기만 하면 됩니다. 컴포지션은 상속이 흔히 만들어내는 강력한 결합 없이 유연성을 제공합니다.

challenge icon

챌린지

쉬움

컴포지션(composition)을 사용하여 컴퓨터 시스템을 구축해 봅시다! 하나의 거대한 구조체를 만드는 대신, 더 작고 집중된 컴포넌트인 CPUMemory를 조합하여 Computer를 구성하게 됩니다. 각 컴포넌트는 자신을 설명하는 방법을 알고 있으며, Computer는 각 부품에 그 역할을 위임합니다.

코드는 세 개의 파일로 구성됩니다:

  • cpu.rs: 두 개의 공개 필드를 가진 공개 CPU 구조체를 정의하세요: 코어 수를 나타내는 cores (u32)와 클럭 속도를 나타내는 speed_ghz (f32)입니다. CPU를 {cores}-core @ {speed_ghz}GHz 형식의 문자열로 설명하여 반환하는 specs 메서드를 추가하세요.
  • memory.rs: 메모리 크기를 나타내는 공개 필드 size_gb (u32)를 가진 공개 Memory 구조체를 정의하세요. {size_gb}GB RAM 형식의 문자열을 반환하는 specs 메서드를 추가하세요.
  • main.rs: 두 모듈을 모두 가져오고, CPU 타입의 cpu 필드와 Memory 타입의 memory 필드를 가지는 공개 Computer 구조체를 만드세요. Computer에 각 컴포넌트에 위임하여 전체 시스템 사양을 출력하는 system_info 메서드를 구현하세요. 제공된 입력을 사용하여 Computer를 빌드하고 정보를 표시하세요.

system_info 메서드는 다음을 출력해야 합니다:

System: {cpu_specs} | {memory_specs}

예를 들어, 입력값이 8, 3.5, 16인 경우:

System: 8-core @ 3.5GHz | 16GB RAM

그리고 입력값이 4, 2.8, 32인 경우:

System: 4-core @ 2.8GHz | 32GB RAM

세 가지 입력을 받게 됩니다: CPU 코어 수(u32로 파싱), CPU 속도(f32로 파싱), 그리고 메모리 크기(u32로 파싱).

치트 시트

Rust는 타입 간에 기능을 공유하기 위해 상속 대신 구성(composition)을 사용합니다. 다른 타입을 필드로 포함하여 복잡한 타입을 구축함으로써 "has-a" 관계를 만듭니다.

기본 구성

작고 집중된 구조체를 정의하고 이를 더 큰 구조체로 구성합니다:

struct Engine {
    horsepower: u32,
}

struct Wheels {
    count: u8,
    diameter: f32,
}

struct Car {
    engine: Engine,
    wheels: Wheels,
    brand: String,
}

컴포넌트에 위임하기

필드를 통해 내부 컴포넌트에 접근하고 해당 메서드를 호출합니다:

impl Engine {
    fn start(&self) {
        println!("Engine with {} HP started!", self.horsepower);
    }
}

impl Car {
    fn start(&self) {
        self.engine.start();  // 내부 컴포넌트에 위임
    }
}

구성은 각 구조체가 단일 책임에 집중하도록 유지하여, 강력한 결합 없이 유연성을 제공합니다.

직접 해보기

mod cpu;
mod memory;

use cpu::CPU;
use memory::Memory;

// TODO: 두 개의 public 필드를 가진 public Computer 구조체를 정의하세요:
// - cpu: CPU
// - memory: Memory

// TODO: Computer에 다음을 수행하는 system_info 메서드를 구현하세요:
// - cpu.specs()와 memory.specs()를 호출하여 위임합니다.
// - 출력 형식: System: {cpu_specs} | {memory_specs}

fn main() {
    let mut input = String::new();
    std::io::stdin().read_line(&mut input).expect("Failed to read line");
    let cores: u32 = input.trim().parse().expect("Invalid number");
    
    input.clear();
    std::io::stdin().read_line(&mut input).expect("Failed to read line");
    let speed_ghz: f32 = input.trim().parse().expect("Invalid number");
    
    input.clear();
    std::io::stdin().read_line(&mut input).expect("Failed to read line");
    let size_gb: u32 = input.trim().parse().expect("Invalid number");
    
    // TODO: 파싱된 입력값을 사용하여 CPU와 Memory 인스턴스를 생성하세요
    
    // TODO: CPU와 Memory를 사용하여 Computer 인스턴스를 생성하세요
    
    // TODO: computer에서 system_info()를 호출하여 결과를 출력하세요
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

Object Oriented Programming의 모든 레슨