Skip to content

第 7 章: カプセル化とポリモーフィズム

7.1 はじめに

第 1 部では手続き型の FizzBuzz プログラムを TDD で構築しました。この章からは オブジェクト指向プログラミング の要素を Rust で実現していきます。まず カプセル化ポリモーフィズム を導入し、手続き型コードを構造化された設計に進化させます。

7.2 手続き型コードの課題

第 1 部で作成した generate 関数は手続き型プログラミングの典型例です。

pub fn generate(number: i32) -> String {
    match (number % 3, number % 5) {
        (0, 0) => "FizzBuzz".to_string(),
        (0, _) => "Fizz".to_string(),
        (_, 0) => "Buzz".to_string(),
        _ => number.to_string(),
    }
}

この設計の課題は、新しい FizzBuzz タイプ(数値のみ返す、FizzBuzz のみ返す等)を追加する場合に、既存の関数を直接修正する必要があることです。

7.3 カプセル化 — 値オブジェクトの導入

FizzBuzzValue 構造体

数値と FizzBuzz の結果をまとめた値オブジェクトを作成します。

#[derive(Debug, Clone, PartialEq)]
pub struct FizzBuzzValue {
    number: i32,
    value: String,
}

impl FizzBuzzValue {
    pub fn new(number: i32, value: String) -> Self {
        Self { number, value }
    }

    pub fn number(&self) -> i32 {
        self.number
    }

    pub fn value(&self) -> &str {
        &self.value
    }
}

impl std::fmt::Display for FizzBuzzValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.value)
    }
}

Rust ではフィールドをデフォルトで非公開(private)にし、メソッド経由でアクセスする設計が標準です。#[derive(Debug, Clone, PartialEq)] は Go の手動実装や Java の equals/hashCode に相当する機能を自動生成します。

7.4 ポリモーフィズム — トレイトの導入

FizzBuzzType トレイト

FizzBuzz のタイプを抽象化するトレイトを定義します。

pub trait FizzBuzzType {
    fn generate(&self, number: i32) -> FizzBuzzValue;
}

3 つの実装

// タイプ 1: 通常の FizzBuzz
pub struct FizzBuzzType01;
impl FizzBuzzType for FizzBuzzType01 {
    fn generate(&self, number: i32) -> FizzBuzzValue {
        let value = match (number % 3, number % 5) {
            (0, 0) => "FizzBuzz".to_string(),
            (0, _) => "Fizz".to_string(),
            (_, 0) => "Buzz".to_string(),
            _ => number.to_string(),
        };
        FizzBuzzValue::new(number, value)
    }
}

// タイプ 2: 数値のみ
pub struct FizzBuzzType02;
impl FizzBuzzType for FizzBuzzType02 {
    fn generate(&self, number: i32) -> FizzBuzzValue {
        FizzBuzzValue::new(number, number.to_string())
    }
}

// タイプ 3: FizzBuzz のみ(Fizz/Buzz/FizzBuzz 以外は空文字)
pub struct FizzBuzzType03;
impl FizzBuzzType for FizzBuzzType03 {
    fn generate(&self, number: i32) -> FizzBuzzValue {
        let value = match (number % 3, number % 5) {
            (0, 0) => "FizzBuzz".to_string(),
            (0, _) => "Fizz".to_string(),
            (_, 0) => "Buzz".to_string(),
            _ => String::new(),
        };
        FizzBuzzValue::new(number, value)
    }
}

trait は Java の interface や Go の暗黙的インターフェースに相当しますが、Rust では impl Trait for Struct で明示的に実装します。

7.5 ファクトリ関数

タイプ番号から適切な実装を返すファクトリ関数を作成します。

pub fn create(type_number: i32) -> Result<Box<dyn FizzBuzzType>, String> {
    match type_number {
        1 => Ok(Box::new(FizzBuzzType01)),
        2 => Ok(Box::new(FizzBuzzType02)),
        3 => Ok(Box::new(FizzBuzzType03)),
        _ => Err(format!("タイプ{}は見つかりません", type_number)),
    }
}

Box<dyn FizzBuzzType> はトレイトオブジェクトで、Java の FizzBuzzType インターフェース型や Go の interface{} に相当します。Result 型で不正なタイプ番号のエラーハンドリングも行います。

7.6 まとめ

この章では以下を学びました。

概念 Rust の実現方法 他言語の対応
カプセル化 struct + 非公開フィールド + メソッド Java: private + getter
ポリモーフィズム trait + impl Trait for Struct Java: interface
値オブジェクト #[derive(PartialEq)] + 不変フィールド Java: equals/hashCode
ファクトリ Result<Box<dyn Trait>> Java: Factory Method

次章では、デザインパターン(Command、First-Class Collection)を適用していきます。