Menu
Coddy logo textTech

コンポジション

CoddyのRustジャーニー「Object Oriented Programming」セクションの一部 — レッスン 55/61。

多くのオブジェクト指向言語は、型の間で機能を共有するために継承に大きく依存しています。Rustは異なるアプローチ、つまりコンポジション(合成)を採用しています。親から振る舞いを継承する代わりに、他の型をフィールドとして含めることで複雑な型を構築します。

車のモデリングを考えてみましょう。ベースとなる Vehicle クラスを作成してそれを継承するのではなく、より小さく、特定の目的に特化したコンポーネントから Car を構成します:

struct Engine {
    horsepower: u32,
}

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

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

CarEngineWheelsを継承しているわけではありません。それらを持っているのです。この「has-a」関係がコンポジションです。フィールドを通じて内部コンポーネントにアクセスし、それらのメソッドを直接呼び出すことができます。

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

impl Car {
    fn start(&self) {
        self.engine.start();  // 内部コンポーネントに委譲する
    }
}

このパターンは、各 struct が単一の責任に集中するようにします。エンジンの仕組みを変更する必要がありますか?Engine struct だけを修正してください。Wheels をオートバイに再利用したいですか?新しい struct にそれを含めるだけです。コンポジションは、継承によって頻繁に発生する密結合なしに柔軟性を提供します。

challenge icon

チャレンジ

簡単

コンポジションを使ってコンピュータシステムを構築しましょう!1つの巨大な構造体を作成する代わりに、より小さく、役割が明確なコンポーネントである CPUMemory から Computer を構成します。各コンポーネントは自分自身を説明する方法を知っており、Computer はその詳細を各パーツに委ねます。

コードは3つのファイルに分けて整理します:

  • cpu.rs: 2つの公開フィールドを持つ公開構造体 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}

例えば、入力が 83.516 の場合:

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

入力が 42.832 の場合:

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

3つの入力を受け取ります:CPUコア数(u32 としてパース)、CPU速度(f32 としてパース)、およびメモリサイズ(u32 としてパース)です。

チートシート

Rustは、型の間で機能を共有するために、継承ではなくコンポジション(合成)を使用します。他の型をフィールドとして含めることで複雑な型を構築し、「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: 2つのパブリックフィールドを持つパブリックな 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のすべてのレッスン