🔨
依存性反転原則についてのメモ(Dependency Inversion Principle)
はじめに
- 依存性反転原則は、オブジェクト指向プログラミングで「疎結合」を実現しようとするとき、制御の向きと依存の向きが必ず逆の関係になることを表している。
- 依存関係が逆転していたらオブジェクト指向プログラミングであり、逆転していなかったら手続き型と言われるくらいオブジェクト指向プログラミングにおいて、重要な考え方である。
- オブジェクト指向で用いられるSOLID原則というものがあり、その原則の一つである。
- Software Design 23年6月号「クリーンアーキテクチャとは何か?」に依存性反転原則について記載されており、自身の理解が及んでない部分であったので、学習した。
SOLID原則
SOLID(ソリッド)は、ソフトウェア工学の用語であり、特にオブジェクト指向で用いられる五つの原則の頭字語である。ソフトウェア設計をより平易かつ柔軟にして保守しやすくすることを目的にしている。その特徴はインターフェースを仲介にしての機能の使用と、インターフェースによる機能の注入である。
この五つの原則は、アメリカのソフトウェア技術者ロバート・C・マーティン(英語版)が提唱していた数々の設計原則の中からチョイスされたものである。
Wikipedia より転載:https://ja.wikipedia.org/wiki/SOLID
依存性反転原則とは
- 上位レベルのモジュールは下位レベルのモジュールに依存すべきではない。両方ともに抽象に依存すべきである。ここでいう上位モジュールは、ユーザが触れる部分が上位側と考える。
- 抽象は詳細に依存してはならない。詳細が抽象に依存すべきである。
- 普通にプログラミングをすると上位が下位に依存する形になるが、下位ではなく、抽象であるインタフェースや抽象クラスに依存した形とするよう意識する。
具体的な実装
- コードは、Software Design 23年6月号「クリーンアーキテクチャとは何か?」より出典する。
依存と制御の向きが同じ結合
- 上位であるドメインモデル側から下位の実現技術側(データゲートウェイ)に依存している。上位が下位に依存している。
package db;
import stock.ProductStock;
public class ProductStockDataGateway {
public ProductStock findOne(String name) {
// データを元に在庫情報オブジェクトを返す
}
}
package stock;
import product.Product;
import db.ProductStockDataGateway;
// 向きがおかしい。
public class ProductPickUp {
private ProductStockDataGateway dataGateway;
public Product pickOne{String name) {
ProductStock stock = dataGateway.findOne(name);
stock.decrement();
return stock.getProduct();
}
}
何が問題となるか。
- 下位モジュールがないと機能しない。つまり、下位モジュールに依存することで、下記モジュールの実装方法に従うことになる。
- 上位モジュールにおける「商品を取得する」という処理が、ProductStockDataGatewayにより、実現方法がすべて決定することになる。接続先がDBでだったりする場合は、「DBへ接続し、商品を取得する」ことになる。
- 上記モジュールにて、「商品を取得する」処理の後、商品数を減らすロジックを実装している。
- 接続先を変える処理を実装したり、DBがまだ用意できておらずダミー応答する場合など上位モジュールで依存先を変える必要がある。その場合、上位モジュール側で「DBへ接続し、商品を取得する」「ダミーデータを取得する」といった出し分けのロジックを上位モジュールで持つことになる。そうした場合、上位モジュールの再利用性は無くなってしまう。
- 上位モジュールとしては、「商品を取得する」という抽象の操作に対して、商品を返却してもらえればそれで良く、具象クラスが具備するような実装は下位モジュール側で対応してほしい。上位モジュールはDB経由であれ、CSV経由であれ、ダミーデータ経由であれ、「商品を取得する」という抽象の操作ができればそれで問題ない。
依存性反転原則に即した抽象化
- インタフェースに依存させて、疎結合にする。
- 「疎結合にする」とは、振る舞いの実装を無視した依存関係を成り立たせること、つまり「インタフェースに依存せよ」ということである。
package stock;
import product.Product;
public interface IProductStockPickingRepository {
public ProductStock findToPickUp(String name);
}
public class ProductStockManager {
private IProductStockPickingRepository repo;
public Product pickOne(String name) {
ProductStock stock = repo.findToPickUp(name)
}
}
package datamapper;
import stock.ProductStock;
import stock.IProductStockPickingRepository;
// 依存がこちら側に移動
public class ProductStockRepositoryImpl implements IProductStockPickingRepository {
// 他のユースケース用のインタフェースも兼ねられる。
public ProductStock findToPickUp(String name){
// ORMを使ってデータをオブジェクトにマップ
}
}
- 抽象(上位モジュール+インタフェース)と実装(下位モジュール)が分離できた形。「商品を取得する」という抽象の操作に対して、具象がORMを使ってデータをオブジェクトにマップする等の実装を具備する。
- 処理の流れはインタフェース▶︎下位モジュールであるが、依存関係は下位モジュール▶︎インタフェースとなっているため、依存関係が逆転していると捉える。クライアントの要求である抽象ロジックをインタフェースとし、下位モジュールで実装する。依存関係が逆転していたらオブジェクト指向プログラミングの形である。
- 下位モジュールの実装がDBであれ、CSVであれ、インタフェース部分で切り替えることで処理をまとめることが可能となる。上位はインタフェースの仕様にのっとるのみ。
DI(Dependency injection)
- 上位モジュールがインタフェース依存する形として、DIの形も存在する。下位モジュールを上位モジュールに与える(注入する)。下位モジュールをインスタンス化して、生成されたインスタンスを上位モジュールに与える。つまり、下位モジュールを外から与えるという考え方である。
- ゲーム機にいろんなソフトウェアを注入するイメージ。ソフトウェアによって、インタフェースであるXボタン、Yボタン押下時の動作が決まる。
Discussion