🦁

オブジェクト指向の原則(SOLID)

に公開

単一責任の原則

単一責任の原則=1つのクラスの変更理由はたった1つのみにする

原則違反で発生する問題

  • 修正箇所を特定するのに時間がかかる
  • 同じような処理が複数箇所に記述されるので、修正後に全ての箇所をテストする必要がある

原則を守ることによる旨み

  • 上記の逆

変更理由という表現は難しいので、最小カプセルを意識しよう

  • 最小カプセル=最小の意味がある単位(カプセル化するインスタンスがバラバラにすると意味がわからなくなるんだったら、それらは同一クラスに保持する)
  • これを意識してると自然とDDDっぽくなる

プログラミングの自問

  • そのクラスは最小カプセルか?
  • モデリングした単位でクラス化できているか?
  • 最小の部品を利用して機能を実現できているか?

オープン・クローズドの原則

オープン・クローズドの原則=拡張に開いて、修正に閉じる

拡張に開いている=機能追加する際に既存のコードを触らず機能追加する

  • 違反例:既存のクラスにif文を追加して機能追加する

修正に閉じている=機能追加や修正する際にクライアントコードを触らずに修正する

  • 違反例:新しいクラスを追加した際にクライアントコードで条件分岐が発生する

原則違反で発生する問題

  • 機嫌よく動いている箇所を触ることになるので、バグの温床になる。
  • 簡単に元に戻せない。
  • 機能が拡張されるたびに条件分岐が増えたりするので、可読性が下がる

原則を守ることによる旨み

  • 上記の逆

オープンクローズドの原則の実装方法

  • インターフェースに対して実装する(ストラテジーパターン)
  • 抽象クラスに対して実装する(テンプレートパターン)
  • 仮想メソッドに対して実装する
    • 非推奨
    • 抽象クラスで実現できるし、デフォルトのロジックを書かないといけない
    • わざわざ具象クラスの実装を見ながら、実装しなくちゃいけないから可読性⤵️

インスタンスの生成部分はファクトリーでやると、可読性⤴️

適切な抽象化

  • 早まった抽象をしないことは、抽象をすることくらい重要
  • なんでもかんでも抽象に実装していたら、可読性が下がり、コード量も増える
  • 同じ動きをするが効果が違うバリデーションがあると予想されれば、抽象する(予想されるバリデーション)
    • 外部接触(mysql,postgresql,oracle)
    • 会員(プラチナ、ゴールド、シルバー)
    • ドラクエの武器(はやぶさのつるぎ、.....)

プログラミングの自問

  • 拡張ポイントを作るべきか?
  • 抽象に対して実装できているか?

リスコフの置換原則

リスコフの置換原則=スーパークラスとサブクラスは交換可能でなければならない

  • 違反例:スーパークラスに存在しないメソッドをサブクラスに定義する

注意点

  • FormクラスやStringクラス等の具象クラスは継承して良い

原則違反で発生する問題

  • クライアントはサブクラスを意識して実装する必要が出てくる
    • サブクラスが増えるたびに条件分岐がクライアント側で増えることになる
    • 知識が漏れて、コードが実装者に依存してしまう(その実装をした人しか扱えないコードになる)

原則を守ることによる旨み

  • 上記の逆

リスコフの置換原則の実装方法

  • スーパークラスに存在しないメソッドをサブクラスに定義しない
  • 抽象に対して実装する

知識を制限する
使う側と使われる側を意識する→何を公開するかを意識する

  • 抽象クラスにロジックを集めて知識が漏れるのを防ぐ
  • protectedを利用してクライアントに見せたくないメソッドをコントロールする
  • サブクラスの知識はサブクラスを作った人だけが知っていれば良い
    • クライアントには簡単に使えるようにする
  • サブクラスのアクセスレベルをinternalにする
    • ファクトリ経由でしかインスタンスを生成できなくする

似てるから継承するという誤り

  • A+Bという関係で継承すると、、、
    • 可読性が下がる(AとB両方を読んでやっと理解できる)
    • サブクラスでどのプロパティやメソッドを使用するのかわからない
    • クライアントには見えすぎてしまって「このメソッド使って良いのかな?」となる
  • 基本的にクラスはfinalで定義する

BはAの機能が欲しい時の継承以外の解決方法

  • AとBを別で作る
    • 本当に同じものなのかを見極める(同じに見えるのは気のせいであることが多い)
    • コードが重複しないのであれば、分けても問題ない
  • BはAを持つ
    • Aの概念自体が欲しい場合は持ってOK
    • Aのメソッドやプロパティが欲しい場合はNG
      • 意図が不明確になる(全部使いたいのかな?)
      • 共通化、最小カプセルを検討
  • 共通化
    • 値とロジックがバラバラになる
      • プログラマーは覚えておかないと実装できない
    • 値に対するロジックではなく、独立したロジックであればOK
  • 最小カプセル化
    • 値とロジックを一緒に切り出してクラスにする
    • 値とロジックの散らばりを防ぐ(値オブジェクト)

プログラミングの自問

  • スーパークラスに存在しないメソッドをサブクラスに定義していないか?
  • 抽象に対して実装できているか?
  • 使う側に知識が漏れていないか?
  • 本当に同じものか?

依存性逆転の原則

依存性逆転の原則=上位のモジュールが下位のモジュールに依存しないようにする、両方とも抽象に依存する
上位のモジュールとは、ユーザーに近い部分(ブラウザ)
下位のモジュールとは、ユーザーから遠い部分(DB)

注意点

  • 全てのモジュールに対してこの原則を適応しなさいということではない。全て従うとプログラムが書けなくなるので。従うことを心がける。
    • Stringクラス等は、ほとんど変化がないクラスなので、依存しても問題ない
    • 判断基準はテストコードがうまく書けるか

原則違反で発生する問題

  • 依存先が実装されていないと依存元は実装できなくなってしまう
  • 依存先を切り替えたい際にクライアントに影響を及ぼす
  • 下位モジュールは上位モジュールに比べて、変化することが多いので、下位モジュールに依存していると影響を受けやすい

原則を守ることによる旨み

  • 上記の逆

依存性の注入(DI)

  • インスタンスを自身で生成しないので、クラス間の依存関係を疎結合にする
  • 外部からインスタンスを渡すことで、一目でどのクラスに依存しているのかがわかる

プログラミングの自問

  • テストコードを書いてからプロダクションコードを書いているか
    • 依存性を注入しないとテストコードが書けなかったら依存性を逆転させる
  • 抽象に対してコーディングしているか

インタフェース分離の原則

インタフェース分離の原則=クライアントが利用しないメソッドの依存を強制してはならない

  • クライアントが本当に必要としているインターフェースのみがクライアントから見えているべき。
  • 一部未実装になってしまうメソッドが存在する場合は、インターフェースを分離することを検討する。
  • Fatインターフェース=インターフェースが権限を持ちすぎている
  • クライアントの要求でインターフェースを分離する

原則違反で発生する問題

  • 意図しない動作をしてしまう可能性がある
  • 意図をクライアントに明確に伝えることができない
    • このメソッドは実行できていいのかな?

原則を守ることによる旨み

  • 上記の逆

ロールとしてのインターフェース

  • インターフェースはロールである
  • 複数の権限(メソッド)を持って、何ができるのかを定義している
  • 着脱可能

クラスが多くのインターフェースを実装するようになったら、、、

  • そのクラスが多くの責務を持っているサインなので、単一責任の原則に従ったクラス設計をする

プログラミングの自問

  • クライアントが本当に利用したいインターフェースのみが見えているか?
  • 一部未実装といったクラスが存在していないか?
  • インターフェース=ロール

Discussion