イミュータビリティのパターン
CoddyのC#ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 34/70。
不変性(Immutability)は、カプセル化を論理的な極致まで高めたものです。一度オブジェクトが作成されると、二度と変更することはできません。これにより、データが予期せず変更されることを心配する必要がなくなるため、あらゆる種類のバグを排除できます。
不変(イミュータブル)なクラスを作成する最も簡単な方法は、すべての値を設定するコンストラクタと共に 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全体で使用されています。例えば、文字列は不変(immutable)です。ToUpper()を呼び出すと、元の文字列を変更するのではなく、新しい文字列が取得されます。
不変オブジェクトは、作成後にどのコードからも変更できないため、本質的にスレッドセーフです。また、状態が予期せず変更されることがないため、動作の理解も容易になります。
チャレンジ
簡単作成後に決して変更されないようにオブジェクトを設計する方法を示す、不変(イミュータブル)な通貨システムを構築しましょう。すべての操作が元のオブジェクトを修正するのではなく、新しいオブジェクトを返す 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インスタンスを返します!- 2つの
Program.cs: メインファイルで、不変性が実際に動作していることを示します。最初のMoneyオブジェクトを作成し、元と結果の両方への参照を保持しながら操作を実行します。これにより、複数の操作の後でも元のオブジェクトが変化しないことが証明されます。
4つの入力を受け取ります:
- 初期金額 (decimal)
- 初期通貨 (string)
- 追加する金額 (decimal)
- EURへの変換レート (decimal)
以下の形式で出力を表示してください:
Original: {GetDisplay()}
After add: {GetDisplay()}
Original unchanged: {GetDisplay()}
Converted: {GetDisplay()}例えば、入力が 100.00、USD、50.00、0.85 の場合、出力は以下のようになります:
Original: 100.00 USD
After add: 150.00 USD
Original unchanged: 100.00 USD
Converted: 127.50 EURAdd() を呼び出した後でも、元の Money オブジェクトが依然として 100.00 USD を示していることに注目してください。これが不変性です! Add() メソッドは 150.00 USD を持つ完全に新しいオブジェクトを作成し、元はそのまま残しました。変換は、加算操作の結果に対して実行されます。
自分で試してみよう
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() を使用して、変換された金額を表示する
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。