こんにちは、サイオステクノロジーの佐藤 陽です。
今回はオブジェクト指向プログラミングの強力な武器の一つである、ポリモーフィズムを利用した依存関係の制御について書きたいと思います。
「依存関係性の制御」や、「依存関係性の逆転(DIP)」などは既に色々なところで解説されていますが
自分の知識定着と、どこかの誰かの役に立つことを期待して書いていきたいと思います。
良ければ最後までご覧ください!
はじめに
先日、今更ながら「Clean Architecture 達人に学ぶソフトウェアの構造と設計」を読んだのですが
その中で個人的に刺さる一文がありました。
それが以下のものです。
OO とは「ポリモーフィズムを使用することで、システムにあるすべてのソースコードの依存関係を絶対的に制御する能力」である。
※OO=オブジェクト指向
この一文に感銘を受け、この気持ちをアウトプットせざるを得なかったため、今回の記事を書くに至っています。
オブジェクト指向プログラミングのメリット
よくオブジェクト指向のプログラミングのメリットとして
- 現実世界に近い形でプログラミングが可能
- カプセル化
- 継承
といったものが挙げられますが、Clean Architectureの書籍の中では
「これらはオブジェクト指向の大きなメリットではない」とバッサリと切り捨てられていました。
(本記事ではこの点には触れないため、気になる方はぜひ 4 章「オブジェクト指向プログラミング」を読んでみてください。)
ではオブジェクト指向の一番の強みとは何かというと、先ほども書いた
「ポリモーフィズムを利用した依存関係の制御」
と述べています。
この点に関して自分なりに整理していきたいと思います。
ポリモーフィズムとは
はじめにポリモーフィズムについて簡単に説明します。
こちらも色々なところで記事にされているので、ここではサクっとだけ。
ポリモーフィズムは「同じ関数であっても、別の動作をするような処理」を実現します。
よく挙げられる例として動物の鳴き声があります。
「鳴く」という動作注目した場合
犬であれば「ワンワン!」、猫であれば「にゃー!」、馬であれば「ヒヒーン!」と鳴きます。
「同じ動物」の同じ「鳴く」という動作であっても、発せられる声は異なってきます。
これを実現するのがポリモーフィズムです。
今回はこれを以下のような形で実施します。
動物(IAnimal)というインターフェイスを定義し、鳴く(MakeSound)というメソッドを定義します。
そしてこの Interface を実装するDog、Cat、Horseのクラスを実装します。
IAnimalのInterface の変数(animal)に各動物のインスタンスを生成したものを代入し、
animalのMakeSound()を呼び出すことで、変数として代入した動物の鳴き声が呼び出されますされます。
IAnimal animal = new ****() //各動物のインスタンスを生成し、IAnimal型の変数に代入する
今回、animal 変数の生成を自前で書いているのであまり恩恵を感じられていませんが
DI コンテナなどの組み合わせることによってポリモーフィズムは非常に強力な真価を発揮します。
現時点では、「同じ関数でも動作を切り替えられるんだなぁ」くらいに思っていただいて OK です。
これを元に、実際に依存関係の制御についてお話していきます。
依存関係の制御
真価を発揮するケース
ではこの「ポリモーフィズムを利用した依存関係の制御」というのが、どういったケースで協力な武器となるか見ていきます。
まずはじめに以下のようなユースケースを想定し、依存関係の制御をする場合と、しない場合について比較を行います。
ユースケース
とあるECサイトを作成するものとします。
そのEC サイトにおいては、以下のような会員ランクの制度があるそうです。
この時、ユーザーが自身の会員ランクを問い合わせる機能を実装します。
具体的な実装の流れとしては以下のような感じになりそうです。
- ControllerがBusinessRuleに対して会員情報を渡してランクを問い合わせる
- DB からユーザー情報に紐づく購入履歴を参照する
- 購入数や購入金額の情報を受け取る
- 購入数や購入金額から会員ランクを算出
- Controllerへ会員ランク情報を返す
依存関係の制御を行わない場合
構成としては以下のようになりそうです。
(※厳密な UML 図ではないです。)
それぞれのクラスの役割は以下の通りです。
Controller | リクエストを受けてユーザー情報を BusinessRule へと渡す |
Business Rule | 購入履歴から会員ランクを判定する |
Database Access | DB へのアクセッサーを担う |
この時の「制御」と「依存」の関係性を図に加えたいと思います。
なお、この時の制御と依存の定義としては以下の通りです。
制御 | メソッドを呼び出して実際に利用する関係 |
依存 |
呼び出す関数を含むモジュールに言及している関係 |
Business Rule が Database Access に対して制御も行っていますし、依存もしている関係性であることが分かります。
ただ、別にこれでも実装自体は行えますし、動作に特段支障も出ないかと思います。
依存関係の制御を行う場合
では先ほどのケースに依存関係の制御を行いたいと思います。
Database Access クラス用に Interface を追加し、それを実装する形にします。
そして、Business Rule のクラスからは Interface を参照するようにします。
図としては以下のような形ですね。
この時、改めて制御と依存の関係性を見てみたいと思います。
ここでポイントなのが、Business Rule と Interfaceを一つの塊として見ることです。
Interface を挟み込み、依存関係の制御を行うことで
依存関係が Database Access –> [Business Rule + IDatabase Access] という方向に逆転していることが分かります。
これこそが「ポリモーフィズムを利用した依存関係の制御」になります。
Interface を挟み込むことによって、開発者が自由自在に依存関係の制御を行えることができるようになるのです。
メリット
依存関係を制御できることによって、何が嬉しいのでしょうか?
一番のメリットとしては、依存関係を反転することで「購入履歴から会員ランクを判断する」というビジネスにとって重要なルールが実装されている Business Rule クラス が何者にも依存しなくなることです。
このルールはビジネスにとって非常に大切なものであり、Databaseの切り替えや、ルールを使う側の都合によって、修正が入ることは望ましくありません。
依存方向を変更することで、Business Rule クラス が Database Access クラス の存在を知る必要がなくなり、Database Access クラスの修正や差し替えが非常に容易になります。
例えば
- 元々はMySQL を使っていたけど Oracle に切り替えたい
- テスト用にダミーデータに差し替えたい
といった事も Business Rule クラスの修正なしに、簡単に行えるようになります。
なぜなら、Business Rule クラス が依存しているのはIDatabase Accessというインターフェイスであり
実際にDatabaseにアクセスするためのクエリが書かれているのは、それを実装しているクラス(Oracle Database Access, MySQL Databas Access etc.)であるためです。
Business Rule クラス からしたら、インターフェイスを実装したクラスがどのような実装になっているか知らないですし、知る必要がありません。
そのため、Database Access クラス の実装がどう変化しようと、 Business Rule クラス の実装に影響は受けないのです。
なおこのように、依存する先をインターフェイスのような安定した抽象にすることを「依存関係逆転の原則(Dependency inversion principle)」と呼びます。
この「安定した抽象」という表現についてですが、
「抽象」に関しては、Interface を指しています。(今回であれば IDatabaseAccess)
対極にある言葉としては「具象」であり、これは Interface を実装したクラスを指します。(今回であれば MySQLDatabaseAccess など)
「安定した」については、書籍には以下のような記載がありました。
優れたソフトウェア設計者やアーキテクトは、インターフェイスの変動制をできるだけ抑えようとする。新しい機能を実装するときにも、できる限りインターフェイスの変更なしで済ませられるようにする。
安定したソフトウェアアーキテクチャは、変化しやすい具象への依存を避け、安定した抽象インターフェイスに依存するべきである。
つまり、インターフェイスは極力修正の必要がない(=安定している状態)ように設計を行うべきということが分かります。
また、このような実装をすることで、インターフェイスに依存するBusiness Ruleのコードの修正も必要なくなることが分かりました。
このように、コードを修正する際に不要な影響を及ぼさない依存関係性にすることを「オープン・クローズドの原則(Open Closed Principle)」と呼びます。
以上のことから、ポリモーフィズムを利用して依存関係を制御し、関係性を逆転させて安定した抽象への依存とすることで
Business Rule の周りがどのようにに変更されようとも、Business Rule に修正を加える必要がなくなり、システムの改修が容易になるのです。
インスタンスの生成
これまで述べてきたような事を実現するためには、
IDataAccess dataAccess = new MySQLDataAccess();
といったような実装をどこかに加えなければいけません。
ただ、これを Business Rule の中に入れてしまっては、結局 MySQLDatabaseAccess に依存する形となってしまい、元も子もありません。
これを解決する実装方法に関しての詳細は今回触れませんが
- DI コンテナを利用する
- Abstract Factory パターンを利用する
といった対応をすることで、依存関係を保ったままインスタンスの生成を行うことができます。
<追記>
DIコンテナを利用するパターンについて、別記事にまとめました。
ASP.NET Coreと銘打っていますが、その点あまり影響なく読めるかと思うのでぜひご覧ください!
まとめ
今回は、オブジェクト指向プログラミングの強力な強みである「ポリモーフィズムを利用した依存関係の制御」について説明しました。
「抽象であるインターフェースに依存するように、ポリモーフィズムを使って依存関係を制御しよう」ということでした。
是非このあたり意識して設計の方を行っていきたいですね。
ではまた!