コンポジション
CoddyのRustジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 55/61。
多くのオブジェクト指向言語では、型間で機能を共有するために継承に大きく依存しています。Rustは異なるアプローチ、つまりコンポジションを採用しています。親から振る舞いを継承する代わりに、他の型をフィールドとして含めることで複雑な型を構築します。
車をモデル化することを考えてみましょう。基底 Vehicle クラスを作成してそこから継承するのではなく、より小さく、目的に特化したコンポーネントから Car を構成します。
struct Engine {
horsepower: u32,
}
struct Wheels {
count: u8,
diameter: f32,
}
struct Car {
engine: Engine,
wheels: Wheels,
brand: String,
}
Car は Engine や Wheels を継承していません。持っています。この「has-a」関係がコンポジションです。fields を通じて内部の component にアクセスし、その method を直接呼び出せます。
impl Engine {
fn start(&self) {
println!("Engine with {} HP started!", self.horsepower);
}
}
impl Car {
fn start(&self) {
self.engine.start(); // 内部コンポーネントに委譲する
}
}
このパターンでは、各構造体を単一の責務に集中させられます。エンジンの動作方法を変更する必要がありますか? Engine 構造体だけを変更します。オートバイで Wheels を再利用したいですか?新しい構造体に含めるだけです。コンポジションを使うと、継承が生みがちな密結合なしに柔軟性を得られます。
チャレンジ
簡単compositionを使ってcomputer systemを構築しましょう!巨大なstructをoneつ作成する代わりに、小さく役割を絞ったcomponent、CPUとMemoryからComputerを構成します。各componentは自身を説明する方法を持ち、Computerはそのパーツに処理を委譲します。
コードをthreeつのファイルに整理します:
cpu.rs:publicなCPUstructをDefineします。このstructには、コア数を表すcores(u32)と、クロック速度を表すspeed_ghz(f32)という2つのpublicなfieldがあります。CPUを{cores}-core @ {speed_ghz}GHzのformatで説明するStringをreturnsするspecsmethodを追加します。memory.rs:memory sizeを表すpublicなsize_gb(u32)fieldを持つ、publicなMemorystructをDefineします。{size_gb}GB RAMのformatでStringをreturnsするspecsmethodを追加します。main.rs:両方のmoduleを取り込み、cpufield(型はCPU)とmemoryfield(型はMemory)をhasするpublicなComputerstructを作成します。Computerにsystem_infomethodをImplementし、そのcomponentに処理を委譲して、system全体の仕様をprintします。提供されたinputsを使ってComputerを構築し、その情報を表示します。
system_info methodは次のようにprintする必要があります:
System: {cpu_specs} | {memory_specs}たとえば、inputsが8、3.5、16の場合:
System: 8-core @ 3.5GHz | 16GB RAMまた、inputsが4、2.8、32の場合:
System: 4-core @ 2.8GHz | 32GB RAMthreeつのinputを受け取ります:CPU cores(u32としてparse)、CPU speed(f32としてparse)、memory size(u32としてparse)です。
自分で試してみよう
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()を呼び出して結果を出力する
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Rustオンラインコンパイラ