オブジェクトの不変性 - 重要な設計思想
不変性は、安定したアプリケーションを構築するための最も重要な設計コンセプトの一つです。基本原則は、一度書き込まれた状態は、修正する可能性がなければ、後から読むことはできない、というものです。状態を変更する必要がある場合は、新しいインスタンスを作成し、オブジェクト全体を別のものに置き換える必要があります。
そのため、データ型は非常に大まかに2つに分類される。
- Mutable (単一インスタンス内で変更可能な状態)
- Immutable(内部状態の不変性)
Mutableオブジェクトは、内部で変更することができます。つまり、異なる組み合わせで呼び出すと、異なる結果が得られるような操作を提供するのである。イミュータビリティは、このような振る舞いを防ごうとするものです。
定義
クラスは、正確にはインスタンス生成後にインスタンスデータを一切変更できない場合、 immutableである。
そのため、すべてのデータはコンストラクタで固定されています。また、すべてのスカラーデータ型は自動的にイミュータブルとなる。
大きなメリット
不変の状態を持つアプリケーションを設計することは、操作の実行の安全性において基本的な利点をもたらす。一度書き込んだデータは後から変更できないことが分かっていれば、例えばデバッグを確実に行うことができますし、アプリケーションをサブファンクションに分割しても、途中の状態を忘れてしまう心配がありません。
不変性という考え方は、一般に、オブジェクト/エンティティのプロパティに状態を保存するという原則に反対するもので、むしろ、例えばjavascriptがそうであるように、データがアプリケーションの中をただ「流れる」ような機能的アプローチを説明するものである。
パフォーマンスの観点からは、イミュータブルオブジェクトは自動的に「無期限にキャッシュできる」と言えます。
PHPの実例
PHP で最もよく使われる immutable オブジェクトは DateTimeImmutable オブジェクトで、一度作成するとフォーマットされたメソッドでしか呼び出すことができません。内部設定を変更した場合、このメソッドは新しいインスタンスを返します。この機能は、いわゆるIDパターンを使用するORMを使用する場合に非常に重要です。例えば、注文の作成日を読み取る際に、アプリケーション内のどこでも同じ日付になり、参照整合性が損なわれないことを保証することができるのです。
ミュータブルオブジェクトの具体例。
$date = new DateTime('2021-05-14');$tomorrow = $date->modify('+1日');echo $date->format('ワイエムディー'); // 2021-05-15echo $tomorrow->format('ワイエムディー'); // 2021-05-15
modify()メソッドはDateTime` オブジェクトの内部状態を変更し、同じインスタンスを返すだけなので、同じ日付が表示されました。したがって、オブジェクト指向プログラミングの基本動作である、いわゆる内部状態の変異がなかったのである。変数を更新すると、元の変数も変更される。
そして今度は、イミュータブルオブジェクトの例です。
$date = new DateTimeImmutable('2021-05-14');$tomorrow = $date->modify('+1日');echo $date->format('ワイエムディー'); // 2021-05-14echo $tomorrow->format('ワイエムディー'); // 2021-05-15
DateTimeImmutable オブジェクトは immutable であり、その内部状態は決して変化しない。modify() メソッドが呼ばれた後、新しい修正されたインスタンス (これも immutable) が変数に格納されます。もし、新しい値を変数に格納しなければ、後で使えなくなる。
オリジナルの価値には一切触れず、安全に保管されたままです。
クラスはいつイミュータブルにすべきなのか?
よっぽどの理由がない限り、クラスや関数は常にイミュータブルに書きましょう。そうすることで、今後の設計がシンプルになります。
Mutableなクラスは、できるだけ変更しないようにします。私は常に、不変性の動作を文書化することを推奨しています。
おそらく不変性の唯一の欠点は、属性を変更するたびに新しいインスタンスを作成する必要があることで、これはパフォーマンスにわずかな影響を与えます。あなたのアプリケーションが(多くのアプリケーションと同様に)データを表示し、それを変更する頻度が少ない傾向がある場合、この欠点は今日のコンピュータの性能ではむしろ軽微なものです。
どのような種類のデータがイミュータブルであるべきなのか?
- すべての識別子と固有コード
- ManyToOneとOneToOneのデータベースセッションの大部分
- 日付、時刻、カレンダー値
- 各要素に対して同じ操作を行いたい配列の循環処理
- インターバル、ペア、トリプル、・・・。
- 幾何学図形、点、線、GPS座標、物理的住所、...。
- ログと履歴の記録
- 処理された注文とほとんどの財務データに関する情報
- 関連するエンティティに関するメタデータ
不変であってはならないもの。
- 多くのプロパティを持つ大きなオブジェクト
- Doctrineエンティティなど、データベースからのほとんどの表形式出力
- 小さな部品から徐々に構築されるオブジェクト