Menu
Coddy logo textTech

イミュータビリティのパターン

CoddyのC#ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 34/70。

不変性はカプセル化を論理的な極致まで高めます。一度オブジェクトが作成されると、二度と変更することはできません。これにより、データが予期せず変更されることを心配する必要がなくなるため、バグのカテゴリ全体を排除できます。

不変(イミュータブル)なクラスを作成する最も簡単な方法は、すべての値を設定するコンストラクタを使用し、セッターを提供しない readonly フィールドを使用することです:

public class Point
{
    public readonly int X;
    public readonly int Y;
    
    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }
}

一度 Point が作成されると、その座標を変更することはできません。しかし、変更されたバージョンが必要な場合はどうすればよいでしょうか?既存のオブジェクトを変更する代わりに、新しいオブジェクトを返します:

public Point Move(int deltaX, int deltaY)
{
    return new Point(X + deltaX, Y + deltaY);
}

元のポイントは変更されず、更新された値を持つ新しいポイントが取得されます。このパターンは .NET 全体で使用されています。たとえば、文字列は不変(イミュータブル)です。ToUpper() を呼び出すと、元の文字列を変更するのではなく、新しい文字列が取得されます。

不変オブジェクトは、作成後にコードがそれらを変更できないため、本質的にスレッドセーフです。また、状態が予期せず変更されることがないとわかっているため、推論も容易になります。

challenge icon

チャレンジ

簡単

作成後に決して変更されないようにオブジェクトを設計する方法を示す、不変(イミュータブル)な通貨システムを構築しましょう。元のオブジェクトを修正するのではなく、すべての操作が新しいオブジェクトを返す Money クラスを作成します。このパターンにより、コードがより安全になり、推論しやすくなります。

コードは2つのファイルに分けて構成します:

  • Money.cs: Finance 名前空間内に不変の Money クラスを作成します。このクラスには以下を含める必要があります:
    • 2つの readonly フィールド: Amount (decimal) と Currency (string)
    • 両方の値を設定するコンストラクタ。一度作成されると、Money オブジェクトは決して変更できません。
    • 増加した金額(同じ通貨)を持つ新しい Money オブジェクトを返す Add(decimal value) メソッド
    • 減少した金額(同じ通貨)を持つ新しい Money オブジェクトを返す Subtract(decimal value) メソッド
    • 金額にレートを掛けた値と新しい通貨を持つ新しい Money オブジェクトを返す Convert(string newCurrency, decimal rate) メソッド
    • "{Amount} {Currency}" という形式の文字列を返す GetDisplay() メソッド
    注意:これらのメソッドはいずれも元のオブジェクトを修正してはいけません。すべて真新しい Money インスタンスを返します!
  • Program.cs: メインファイルで、不変性が実際に動作していることを示します。最初の Money オブジェクトを作成し、元と結果の両方への参照を保持しながら操作を実行します。これにより、複数の操作の後でも元のオブジェクトが変化しないことが証明されます。

4つの入力を受け取ります:

  • 初期金額 (decimal)
  • 初期通貨 (string)
  • 追加する金額 (decimal)
  • EURへの変換レート (decimal)

以下の形式で出力を表示してください:

Original: {GetDisplay()}
After add: {GetDisplay()}
Original unchanged: {GetDisplay()}
Converted: {GetDisplay()}

例えば、入力が 100.00USD50.000.85 の場合、出力は以下のようになります:

Original: 100.00 USD
After add: 150.00 USD
Original unchanged: 100.00 USD
Converted: 127.50 EUR

Add() を呼び出した後でも、元の Money オブジェクトが依然として 100.00 USD を示していることに注目してください。これが不変性です! Add() メソッドは 150.00 USD を持つ完全に新しいオブジェクトを作成し、元はそのまま残しました。変換は、加算操作の結果に対して実行されます。

チートシート

不変性(イミュータビリティ)とは、一度オブジェクトが作成されると、それを決して変更できないことを意味します。これにより、予期しないデータの変更に関連するバグを排除できます。

コンストラクタを使用し、セッターを持たない readonly フィールドを使用して、不変クラスを作成します。

public class Point
{
    public readonly int X;
    public readonly int Y;
    
    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }
}

不変オブジェクトを修正するには、既存のオブジェクトを変更するのではなく、新しいインスタンスを返します。

public Point Move(int deltaX, int deltaY)
{
    return new Point(X + deltaX, Y + deltaY);
}

不変オブジェクトの利点:

  • 作成後にコードを変更できないため、設計上スレッドセーフである
  • 状態が予期せず変化することがないため、推論が容易である
  • .NET全体で使用されている(例:文字列は不変である)

自分で試してみよう

using System;
using Finance;

class Program
{
    public static void Main(string[] args)
    {
        // 入力を読み込む
        decimal initialAmount = Convert.ToDecimal(Console.ReadLine());
        string initialCurrency = Console.ReadLine();
        decimal amountToAdd = Convert.ToDecimal(Console.ReadLine());
        decimal conversionRate = Convert.ToDecimal(Console.ReadLine());

        // TODO: initial amount と currency を使用して、初期の Money オブジェクトを作成する

        // TODO: GetDisplay() を使用して、元の金額を表示する

        // TODO: 元のオブジェクトに対して Add() を呼び出し、その結果を新しい変数に格納する

        // TODO: 加算後の結果を GetDisplay() を使用して表示する

        // TODO: 変更されていないことを証明するために、もう一度元の金額を表示する

        // TODO: conversion rate を使用して、加算後の結果を EUR に変換する

        // TODO: 変換された金額を GetDisplay() を使用して表示する
    }
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン