Python で ODD
目次
クラス作成の指針
- アプリ独自データとその計算処理をまとめる
- とにかく組み込み型では表現できないデータ構造(値域・複合等)をクラス定義。実装が進んできたら削除・統合。
- アプリの構成軸でクラス定義
- 計算判断(ドメインモデル)クラス群🔢
- 他クラス群に依存しない。
- アプリ独自データとそれに必要な基本的な演算規則のみを定義。
- 計算判断集約クラスでより複雑な計算処理をする。
- アプリケーションサービスクラス群🧩
- 他クラス群のコンポジションで構成。
- データソースクラス群とは interface で接続方法を定義。取得・記録をトリガー。
- ユーザとの操作・通知はプレゼンテーションクラス群を介して実施。
- 細かい計算ロジックは計算判断クラス群に任せる。
- アプリの状態を保持・変更。
- プレゼンテーションクラス群💬
- 外部との入出力。リクエスト受信。結果を表示・返却。
- 外部からアプリケーションサービスクラスのメソッドをトリガー。
- データソースクラス群🗄️
- データベース操作クラスと通信アダプタークラス。
- データベース操作クラスは interface を介して定義。データ(ベース)フォーマットとクラスを双方向に変換。
- 通信アダプタークラスは外部へのアクセスとデータの変換。
- 計算判断(ドメインモデル)クラス群🔢
基本的なリファクタリング手法
- メソッドに関する問題
- メソッドの引数が多い:引数のグループ化とグループのクラス化
- メソッドが長い:対象メソッドのみを持つ新しいクラスを定義・引数をメンバ変数化
ifのネストが深い- 事前処理・事後処理の分岐を分離
- 深い箇所の分岐・処理をメソッド化(ヘルパーメソッド)
- 長い分岐条件をメソッド化・長い処理をメソッド化
forのネストが深いifのケースと同じ- 加えてループ対象のデータの構造をクラス化(コレクションオブジェクト)してメンバ変数として持つ
- メンバ変数に関する問題
- メンバ変数が多い:メンバ変数を使用するメソッドの観点からグループ化。各グループでクラス分割。
- メンバ変数がない・少ない:メソッドのクラス化。引数をメンバとして持つクラスを独立して作成。
SOLID 原則
- イラストで理解するSOLID原則
- S:単一責任の原則 (Single Responsibility)
- 概要:クラスを単一機能になるまで分割
- アンチパターン:God クラス・Manager クラスができる
- O:オープン・クローズドの原則(Open-Closed Principle)
- 概要
- オブジェクトに対して追加実装の余地がある(Open)
- 追加実装で既存機能の編集を要しない(Close)
- アンチパターン:引数の内容に応じて分岐処理
- デザインパターン:Factory Method
- 機能改変時に処理オブジェクトは変更せずにデータオブジェクトのみを変更できるようにする
- 概要
- L:リスコフの置換原則(Liskov Substitution)
- 概要:子クラスは親クラスのインターフェースを引き継ぐべき
- 備考:この原則を満たさないケースでは継承より委譲
- I:インターフェイス分離の原則 (Interface Segregation)
- 概要:必須のインタフェースのみになるようクラスを分割
- 備考:共通化で使わないインタフェースが現れる場合は共通化をしない
- D:依存性逆転の原則(Dependency Inversion Principle)
- 概要:上位・下位オブジェクトの依存は抽象化。具体実装に依存しない。
- アンチパターン:上位オブジェクト内で下位オブジェクトのインスタンスを生成し使用
- デザインパターン:ダックタイピング(C++ ならテンプレート)
- Dependency Injection:内部でインスタンスを生成せずに引数として受け取る
- 受け取るオブジェクトにインタフェースを定義してインタフェースに依存
- インタフェースを受け取り側に含める → ダックタイピング
GoF のデザインパターン
GoF のレビュー:
- Python におけるデザインパターン
- シンプルな説明
- Refactoring.Guru
- 見やすい説明・複数言語での実装
- ふと GoF のデザインパターンを再考しておく
- 実際の使われ方
- 再考: GoF デザインパターン
- OOP の現代思想に照らしたコメント
生成に関するパターン
- Factory の比較
- Factory:生成に関するクラス・メソッド・デザインパターン全般
- 生成メソッド:新しいオブジェクトを生成するメソッド。コンストラクタのラッパー。
- 既存のインスタンスををベースに生成可能
- private の内容も使用して生成が可能
- 静的生成メソッド:インスタンスに依存しない生成メソッド
- インスタンスを生成のポリモーフィズムで型が重複する(オーバーロードで対応できない)ケースで便利
- 単純ファクトリーパターン:引数に応じた分岐で生成するインスタンスを切り替える
- Factory 系デザインパターンのアンチパターン
- Factory Method パターン:サブクラスでインスタンス生成のメソッドを定義・拡張
- 生成専用メソッドでコンストラクタの肥大化を避ける
- 具象化するまで生成方法を定義しない
- サブクラスの選択で生成内容が切り替えられる
- Abstruct Factory パターン:Factory クラスの選択で生成内容を切り替える
- 生成専用クラスでコンストラクタの肥大化を避ける
- Factory クラス群に対して抽象基底クラスを定義するから Abstruct Factory パターン
- 抽象基底クラスの継承の代わりにダックタイピングを使っても OK
Factory Method パターン
- 概要:(サブクラスで)生成用のメソッドを定義
- 使い所:
- オブジェクト生成が複雑
- インターフェースが共通だが生成過程が異なるオブジェクトが複数存在
- 使用方法
- 抽象基底クラスでは生成に関するメソッドを abstractmethod とする。継承時に定義。
- あるいは(継承を使わずに)クラス自体を引数として与えダックタイピングを使う(Dependency Injection)
Abstruct Factory パターン
- 概要:オブジェクト生成用のクラスを定義
- 使い所:
- オブジェクト生成が複雑
- 生成過程が似ているオブジェクトが複数存在
- 使用方法:
- オブジェクトの生成過程を分割
- 複数クラスが対象なら共通部分と非共通部分が別になるように分割
- 分割した手順をそれぞれメソッドに持つ Factory クラスを定義
- 生成したいオブジェクトが複数種ある場合はそれ毎に Facotry クラスを定義
- 必要であれば共通部分について抽象基底クラスを定義
- 生成用の関数を定義
- 引数に与える Factory クラスのインスタンスに応じてオブジェクトの生成結果が異なる
- Factory の抽象基底クラスを定義しない場合はダックタイピングを使うことになる
- オブジェクトの生成過程を分割
Builder パターン
- 概要:Factory を用いた生成のためのクラスを定義
- 使い所:Factory を用いた生成で複数のケースが必要
- 使用方法:Builder を引数にもつ staticmethod を持つクラス(Director)を定義して使う
Prototype パターン
- 概要:生成コストが高いクラスはできるだけコンストラクタを使わずにインスタンスのコピーで生成
- 使い所:Python で copy.deepcopy を使うところそのもの。
- 備考:
- np.ndarray.copy のようにコピーされる側がコピーするためのメソッドを定義してもよい
Singleton パターン
- 概要:インスタンスの生成が1度のみであればコンストラクタ等を隠蔽して classmethod でアクセス
- 備考:
- モジュール同士の密結合を起こしかねないので注意
- 使用するには単なるグローバル変数の使用との明確な差別化が必要