第3章 クラス設計 ―すべてにつながる設計の基盤―
保守や変更がしやすいコードを実現するためには、関心の分離が重要です。
オブジェクト指向は、この関心の分離を実現するのに適した手法といえます。
本章では、オブジェクト指向そのものの理論ではなく、実務での具体的な活用方法を中心に解説します。特に、クラス設計について詳しく取り上げます。また、「データクラス=悪魔」という考え方も紹介します。
3.1 クラス単体で正常に動作するよう設計する
クラス設計の基本は、単体で正常に動作するように設計することです。
設計のポイント
- 初期設定不要で、最初から利用可能な状態にする。
- バグを防ぐため、正しく操作できるメソッドのみを外部に公開する。
良いクラスの構成要素
- インスタンス変数
- インスタンス変数を保護し、正常に操作するメソッド
これらを分けて実装すると、相互関係を把握するのが難しくなります。
データクラスが問題となる理由
- 他のクラスがデータクラスの準備や検証を担う必要が生じる。
解決策:データクラスを改善する
- 自己防衛責務を持たせ、正常な値のみ扱うメソッドを定義します。
3.2 成熟したクラスへ成長させる設計術
クラスを成熟させるためには、以下のポイントを押さえる必要があります。
コンストラクタで確実に正常値を設定する
- メンバ変数をすべて設定可能にする。
- 入力値のバリデーションを行う。
ガード節を設ける
- 不正値を初期段階で除外し、不正なオブジェクトが存在しないようにする。
インスタンス変数は不変にする
- 変数を上書きしないことで、コードの理解が容易になる。
- 値を変えたい場合は、新しいインスタンスを作成する。
その他の推奨事項
- 引数やローカル変数も不変にする。
- 引数の型を明確に定義し、値の渡し間違いを防ぐ。
- 必要なメソッド以外は定義しない(例:金額 × 金額のような意味のない操作を避ける)。
所感
- C++ではコンストラクタで例外を投げるとメモリリークの原因になるため、言語ごとに設計の工夫が必要。
- メンバ変数が多い場合、コンストラクタの引数が複雑化する問題がある。
- Setterを排除する設計には疑問もあり、状況に応じた柔軟な対応が求められる。
3.3 悪魔退治の効果を検証する
問題点とその解決
問題点 | 3.2 の取り組みの結果 |
---|---|
重複コード | 必要なロジックが一つのクラスに集約された |
修正漏れ | 重複コード解消で修正漏れが減少 |
可読性低下 | ロジックを探し回る必要がなくなった |
生焼けオブジェクト | コンストラクタで初期化され、未初期化状態がなくなった |
不正値の混入 | ガード節により防止 |
思わぬ副作用 | Immutable設計で副作用を排除 |
値の渡し間違い | 型指定によりコンパイル時に防止可能 |
用語解説
- 低凝集: 関連ロジックが散在している状態。
- 高凝集: 関連ロジックが一箇所に集約されている状態。
- カプセル化: データとその操作ロジックをクラスにまとめ、必要なメソッドのみ外部に公開する。
3.4 プログラム構造の問題解決に役立つ設計パターン
設計パターンの例
- 完全コンストラクタ
- 値オブジェクト
- 他の例
- ストラテジ
- ポリシー
- ファーストクラスコレクション
- スプラウトクラス
完全コンストラクタ
インスタンスをすべてコンストラクタで初期化することで、不正状態から防護する設計パターン。
Setterを用意しないことで、未初期化のリスクを排除します。
値オブジェクト (Value Object)
値をクラス(型)として表現する設計パターン。
例: 金額、日付、注文数など。これにより、ロジックの高凝集化が可能となります。
Column: 言語ごとの差異
言語によって提供される機能や思想は異なりますが、基本的な考え方は活かせます。
例: freeze()
のような不変化機能は多くの言語で提供されていますが、独自実装が必要な場合もあります。
所感
- 言語や環境によって異なる特性を考慮しながら設計を進める必要があります。
- 値オブジェクトを設計する際、ドメイン知識が欠けると適切な設計が難しい場合もあります。
方針
社内メンバと勉強会を行った結果、下記の方針で実装を行うこととなった
- 少しずつ Value Object を作っていく
- 完全コンストラクタを意識するようにする
- 状態の Value Object を持つ Instance 変数は変更可能
- 値を利用する際には Getter を経由して、ORM 経由でも必ず Value Object で扱うようにする
- 意識的新しい Class (Value Object) をつくるようにしていく
- 上記方針で運用した結果の振り返りを行う