概念的な話
クラスが担う責任はたった一つにするべきという原則。クラスを仕様変更する理由を一つにする。(複数の仕様で一つのクラスを使い回さない)
「単一責任の原則」は二つの側面がある。
単一責任の原則は人によって言っていることが微妙に変わる印象を受けました。
- クラスの汎用性を上げるために意味がわかる最小単位までクラスを分けること
- 「仕様の変更」において一つの理由がある単位までクラスを分けること
前者は「細かい粒度」、後者は「大きな粒度」でクラスを分ける場合の考え方のような気がします。
クラスの汎用性を上げるために意味がわかる最小単位までクラスを分けること
なので、実際に意識することとしては基本クラスって「値」と「メソッド」を持つものだと思うのですが基本的にはこれ以上分けられないくらいまでクラスを最小化すること(これ以上分けてしまうとこのクラスの意味が通じなくなってしまうなと思える単位まで最小化する)だけを意識すれば良いです。変更理由がどうとか考え始めると話が非常にややこしくなってしまうと思いますので。(基本的には、インスタンス変数を使ってないメソッドが発生してきたらそのメソッドは怪しいという目で見ていただく形だと思います。)
例
ユーザークラスがあった場合に以下のようなインスタンス変数を思い浮かべます。
- ユーザーID
- ユーザー名
- 住所
ユーザーIDや、ユーザー名、住所なども分けるかという点ですが、結論から言えばこれから実装するシステムによります。例えば、ユーザーIDなどが独自のルールを持っていたりだとかする場合はユーザーIDクラスなどのように分けた方が良いでしょう。
仕様変更において一つの理由がある単位までクラスを分けること
例1
家具の販売システムを構築していたとして、「家具の入力フォームを編集するメソッド」があったとして、別機能として「販売管理系の入力フォームを編集するメソッド」が後から追加される場合に「家具の入力フォームと似た作りになってるからそちらのメソッドを呼んだ方がDRYになるから呼ぼう」という考え方ではNGになります。将来的に、「家具の入力フォームだけ機能改修が入った場合」に「販売管理系のフォームに影響がないか」を把握しておかなければならないためです。
単一責任を守った上での共通化とは?
なので、「ビジネスロジックを共通化する」のは仕様変更の影響を受けてしまうので基本的にはNGです。
単一責任を守った上で共通化するのであれば、例えば、フォームの中のテキストボックスだとか小さい粒度をバリューオブジェクト化して共通化していくなどが良いでしょう。そういった小さい粒度の要素は仕様変更の影響を受けにくいためです。(テキストボックスクラスを作って保持する状態のバリデーションロジックなどは共通化できると思います。)
フラグを渡して、条件分岐させるのはどうか
フラグを渡して共通処理の中で分岐させるのも以下の理由からNGになります。
- 条件分岐で処理が肥大化して可読性が下がる。
- 分岐があるとメソッド名から何をやっているかすぐに処理内容を類推することができなくなる。
できるだけ分岐を使わず一つの仕様は単一のクラスで処理するような実装にしましょう。
例2
「受注画面クラス」があった場合以下のような処理を全て同じクラスに記述してしまうと単一責任の原則に反します。
- 受注データの入力
- 受注データのDB登録
- 登録済みデータのメール送信
メール送信処理にBCCも追加する修正を加えるとなった際に、「受注画面クラスを変更」しなければなりません。
修正例
以下のように分けること。
- 受注画面クラス
- 受注データアクセスクラス(DBアクセス)
- メール送信クラス
よくある分け方
この三つに分けるのもよくありがちなパターンだと思いますが、これも「単一責任の原則」を考慮した上での分け方になります。
- 画面
- ビジネスロジック
- データアクセス
画面
ビジネスロジック
頻繁に変化する。
データアクセス
インフラが変わった場合などに変更することになるだけで、頻繁には変化しない。
その他デメリット
ある責務を期待してそのクラスを利用しようとしたときに、期待していない機能までついてきてクラスの再利用性が低くなる。
この記事へのコメントはありません。