1. 問題点:密結合なコード
ソフトウェアの部品同士が強く結びつきすぎている状態を「密結合」と呼びます。例えば、`ReportGenerator`(レポート生成)クラスが、内部で`MySQLConnection`(MySQLデータベース接続)クラスを直接`new`している状況を考えてみましょう。
依存関係の図
(new MySQLConnection)
class ReportGenerator {
private:
MySQLConnection* dbConnection;
public:
ReportGenerator() {
// 内部で具体的なクラスを直接生成!
this->dbConnection = new MySQLConnection();
}
// ...
};
この設計の問題点
- 柔軟性がない: データベースをPostgreSQLに変えたくなったら、`ReportGenerator`のコードを直接修正する必要があります。
- テストが難しい: テスト時に本物のデータベースではなく、偽のデータ(モック)を使いたい場合でも、`MySQLConnection`が強制的に作られてしまいます。
2. 解決策の原則:依存性逆転の原則 (DIP)
この問題を解決するのが**依存性逆転の原則 (Dependency Inversion Principle)** です。これは「具体的な実装」に依存するのではなく、お互いに**「抽象(インターフェースや抽象クラス)」**に依存しましょう、というルールです。
逆転した依存関係の図
`ReportGenerator`も`MySQLConnection`も、具体的なお互いではなく、抽象的な`IDatabaseConnection`に依存しています。
class IDatabaseConnection {
public:
virtual ~IDatabaseConnection() {}
virtual void connect() = 0;
};
// ReportGeneratorは抽象に依存
class ReportGenerator {
private:
IDatabaseConnection* dbConnection;
// ...
};
3. 実現の仕組み:依存性の注入 (DI)
依存性逆転の原則を実現するための具体的なテクニックが**依存性の注入 (Dependency Injection)** です。これは、クラスが必要とするオブジェクト(依存オブジェクト)を、**内部で`new`するのではなく、外から与えてもらう(注入してもらう)**という設計です。
class ReportGenerator {
private:
IDatabaseConnection* dbConnection;
public:
// コンストラクタで外から依存オブジェクトを受け取る
ReportGenerator(IDatabaseConnection* connection) {
this->dbConnection = connection;
}
// ...
};
インタラクティブ・デモ
下のボタンをクリックして、`ReportGenerator`に異なるデータベース接続を「注入」してみましょう。`ReportGenerator`のコードを一切変更せずに、その振る舞いが変わることがわかります。
ここにレポート生成の結果が表示されます...
4. DIとDIPがもたらすメリット
テスト容易性
本物のデータベースの代わりに、テスト用の偽オブジェクト(モック)を簡単に注入できるため、単体テストが非常に書きやすくなります。
柔軟性と拡張性
新しいデータベース(例: `SQLiteConnection`)が追加されても、`ReportGenerator`を変更する必要はありません。新しい部品を差し替えるだけです。
保守性
各クラスが自身の責任に集中でき、他のクラスの実装詳細を知らなくてよいため、コードの理解や修正が容易になります。