Menu
Coddy logo textTech

트레이트 객체 순회하기

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

이제 Vec<Box<dyn Trait>>에 다양한 타입을 저장할 수 있게 되었으므로, 다음 단계는 해당 컬렉션을 처리하는 것입니다. 트레이트 객체(trait objects)를 순회하는 것은 일반적인 벡터를 순회하는 것과 똑같이 작동합니다. 즉, 각 요소를 루프하며 트레이트에 정의된 메서드를 호출하면 됩니다.

trait Shape {
    fn area(&self) -> f64;
}

struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }

impl Shape for Circle {
    fn area(&self) -> f64 { 3.14159 * self.radius * self.radius }
}

impl Shape for Rectangle {
    fn area(&self) -> f64 { self.width * self.height }
}

fn main() {
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { radius: 2.0 }),
        Box::new(Rectangle { width: 3.0, height: 4.0 }),
        Box::new(Circle { radius: 1.0 }),
    ];

    for shape in &shapes {
        println!("Area: {}", shape.area());
    }
}

for shape in &shapes를 사용하여 반복할 때, 각 shapeBox<dyn Shape>에 대한 참조입니다. Shape 트레이트의 모든 메서드를 직접 호출할 수 있습니다. Rust는 동적 디스패치를 자동으로 처리하여 각 구체적인 타입에 맞는 올바른 구현을 호출합니다.

이 패턴은 결과를 집계하는 데 강력합니다. 모든 면적을 합산하거나, 크기별로 도형을 필터링하거나, 컬렉션을 변환할 수 있습니다. 핵심적인 통찰은 항목들이 트레이트 객체(trait object) 컬렉션에 들어가면, 원래의 타입에 관계없이 순수하게 트레이트의 인터페이스를 통해서만 상호작용한다는 점입니다.

challenge icon

챌린지

쉬움

다양한 유형의 항목을 담을 수 있는 쇼핑 카트용 가격 계산기를 만들어 봅시다! 서로 다른 가격 책정 로직을 가진 제품(Product)과 서비스(Service)를 함께 저장하고 처리하여 총 가격을 계산하는 시스템을 구축하게 됩니다.

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

  • items.rs: &self를 매개변수로 받고 f64를 반환하는 price 메서드를 가진 공개(public) Priceable 트레이트를 정의합니다. 그런 다음 두 개의 공개 구조체를 만듭니다:
    • Product — 공개 name (String) 및 cost (f64) 필드를 가집니다. price는 단순히 cost를 반환해야 합니다.
    • Service — 공개 name (String), hourly_rate (f64), hours (f64) 필드를 가집니다. pricehourly_rate * hours를 반환해야 합니다.
  • main.rs: items 모듈을 가져와서 Vec<Box<dyn Priceable>> 타입의 벡터를 생성합니다. 입력을 사용하여 하나의 Product와 하나의 Service를 생성하고 벡터에 추가한 다음, 컬렉션을 반복하여 모든 항목의 총 가격을 계산하고 출력합니다.

여기서 트레이트 객체(trait objects)의 강력함이 드러납니다. 반복문 코드는 처리 중인 항목이 Product인지 Service인지 알 필요가 없습니다. 단순히 각 항목에 대해 price()를 호출하면, Rust가 자동으로 올바른 구현체로 디스패치합니다.

출력은 소수점 첫째 자리까지 총 가격을 표시해야 합니다:

Total: ${total}

예를 들어, 입력값이 Laptop, 999.99, Consulting, 150.0, 3.0인 경우:

Total: $1449.99

그리고 입력값이 Book, 29.99, Tutoring, 50.0, 2.0인 경우:

Total: $129.99

제품 이름, 제품 비용, 서비스 이름, 시간당 요금, 작업 시간 등 5개의 입력을 받게 됩니다(숫자 값은 f64로 파싱하세요).

치트 시트

트레이트 객체 컬렉션을 반복하려면 벡터에 대한 참조와 함께 표준 for 루프를 사용하세요:

for item in &collection {
    item.trait_method();
}

for item in &vec를 사용하여 반복할 때, 각 itemBox<dyn Trait>에 대한 참조입니다. 트레이트의 모든 메서드를 직접 호출할 수 있습니다. Rust는 동적 디스패치를 자동으로 처리하여 각 구체적인 타입에 맞는 올바른 구현을 호출합니다.

결과를 집계하기 위해 트레이트 객체를 반복하는 예시입니다:

trait Shape {
    fn area(&self) -> f64;
}

struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }

impl Shape for Circle {
    fn area(&self) -> f64 { 3.14159 * self.radius * self.radius }
}

impl Shape for Rectangle {
    fn area(&self) -> f64 { self.width * self.height }
}

fn main() {
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { radius: 2.0 }),
        Box::new(Rectangle { width: 3.0, height: 4.0 }),
    ];

    for shape in &shapes {
        println!("Area: {}", shape.area());
    }
}

이 패턴은 결과를 집계하는 데 유용합니다. 트레이트 인터페이스를 통해서만 아이템과 상호작용하면서 값을 합산하거나, 기준에 따라 필터링하거나, 컬렉션을 변환할 수 있습니다.

직접 해보기

mod items;

use items::{Priceable, Product, Service};

fn main() {
    // 입력 읽기
    let mut product_name = String::new();
    std::io::stdin().read_line(&mut product_name).expect("Failed to read line");
    let product_name = product_name.trim().to_string();

    let mut product_cost = String::new();
    std::io::stdin().read_line(&mut product_cost).expect("Failed to read line");
    let product_cost: f64 = product_cost.trim().parse().expect("Failed to parse");

    let mut service_name = String::new();
    std::io::stdin().read_line(&mut service_name).expect("Failed to read line");
    let service_name = service_name.trim().to_string();

    let mut hourly_rate = String::new();
    std::io::stdin().read_line(&mut hourly_rate).expect("Failed to read line");
    let hourly_rate: f64 = hourly_rate.trim().parse().expect("Failed to parse");

    let mut hours = String::new();
    std::io::stdin().read_line(&mut hours).expect("Failed to read line");
    let hours: f64 = hours.trim().parse().expect("Failed to parse");

    // TODO: product_name과 product_cost를 사용하여 Product 생성

    // TODO: service_name, hourly_rate, hours를 사용하여 Service 생성

    // TODO: Vec<Box<dyn Priceable>>을 생성하고 두 항목을 모두 추가

    // TODO: 벡터를 순회하며 총 가격 계산

    // TODO: 소수점 첫째 자리까지 총액 출력
    // 형식: Total: ${total:.1}
}
quiz icon실력 점검

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

Object Oriented Programming의 모든 레슨