依存関係逆転の原則とDI(Dependency Injection)
業務の中で「依存関係逆転の原則」という原則とそれに関わるデザインパターンの「DI(Dependency Injection)」について、学ぶ機会があったので、ここに書き記します。
動作環境 C#
依存関係逆転の原則とは?
ソフトウェア設計においての概念で す。
この原則には以下二つのルールがあります。
- 高レベルのモジュールは低レベルのモジュールに依存してはならない。両者は抽象に依存すべきである
- 抽象は詳細に依存してはならない。詳細は抽象に依存すべきである。
ここで言う、抽象は「インターフェース」、詳細は「具体的な実装」のことだと思ってください。
つまり、他クラスのメソッド等を使いたいときはそのクラスに依存するようなコードでは無く、インターフェースに依存するようなコードにしましょうということです。
そのようなプログラムを作りたいときに使われるのが、DI(Dependency Injection)です。
DIとは?
デザインパターンの一つです。プログラムの必要な部分を外部から入れてあげるような手法です。この手法を使用することによりテストが簡単になったり、コードを再利用しやすくなります。またコードの柔軟性も向上します
この二つをふわっと理解しながら、実際にプログラムで考えてみましょう。
例えばロボット作りで考えてみます。
ロボットには燃料が必要ですよね。その燃料を入れる部分をDIを使わずに作ると以下のようになります。
public class Program
{
static void Main(string[] args)
{
Robot robot = new Robot();
Electricity electricity = new Electricity();
// ロボットに電気を渡す
robot.Start(electricity);
}
}
public class Robot
{
public void Start(Electricity electricity)
{
electricity = new Electricity();
// ロボットに燃料を注入する
electricity.SupplyElectricityPower();
}
}
public class Electricity
{
public void SupplyElectricityPower()
{
Console.WriteLine("電気を注入します");
}
}
この例では、ロボットの燃料には電気(Electricity)が使われています。
メイン部分でRobotクラスにインスタンス化したElectricityクラスを渡しています。またRobotクラスではElectricityクラスのSupplyElectricityPowerメソッドを使っています。
この状態だとRobotクラスはElectricityクラスを必ず使わないと動かないため、RobotクラスはElectricityクラスに依存している状態となっています。
次に今回のテーマであるDI(Dependency Injection)の手法を使うと次のようになります。
public class Program
{
static void Main(string[] args)
{
IEnergy electricity = new Electricity();
// ロボットに電気を渡す
Robot robot = new Robot(electricity);
robot.Start();
}
}
public class Robot
{
private IEnergy _energy;
public Robot(IEnergy energy)
{
_energy = energy;
}
public void Start()
{
// ロボットに燃料を注入する
_energy.SupplyPower();
}
}
// 新しくインターフェースを作成
public interface IEnergy
{
public void SupplyPower();
}
// インタフェースを継承してクラスを作成
public class Electricity : IEnergy
{
public void SupplyPower()
{
Console.WriteLine("電気を注入します");
}
}
IEnergyというインターフェースを作成し、Electricityクラスはそれを継承したものとなっています。またRobotクラスはIEnergyインターフェースを継承したクラスを 入れることで動作するようになっています。
このようにするとRobotクラスはElectricityクラスに依存することなく、動作させることができます。
何が良いのか?
- コードの再利用性
例えば、ロボットの燃料が電気ではなく、ガソリンに変わったとしましょう。
その場合、IEnergyを継承した新しくガソリンを取り扱うクラスを用意し、その中にガソリンを注入するメソッドを用意することになります。
public class Gasoline : IEnergy
{
public void SupplyPower()
{
Console.WriteLine("ガソリンを注入します");
}
}
DIを使っているとRobotクラスはの中身は変えずにGasolineクラスを使うことができます。
public class Program
{
static void Main(string[] args)
{
// 電機ではなくガソリンに変更
//IEnergy electricity = new Electricity();
IEnergy gasoline = new Gasoline();
// ロボットにガソリンを渡す
Robot robot = new Robot(gasoline);
robot.Start();
}
}
// Robotクラスの中身は変えなくていい
public class Robot
{
private IEnergy _energy;
public Robot(IEnergy energy)
{
_energy = energy;
}
public void Start()
{
// ロボットに燃料を注入する
_energy.SupplyPower();
}
}
- テストのしやすさ
Robotクラスのテストをする際に、Gasolineクラスを必ず用意しなくて も、Energyインターフェースを経由したテスト用のクラスを作成しテストが行えます。
実際にあるような状況で考えてみましょう。
例えば、500人分の従業員の給料を計算し、明細にして印刷するようなプログラムがあるとします。
必要なクラスは「従業員のデータを取得するクラス」、データをもとに「計算するクラス」、給料明細を「印刷するクラス」の3つです。この一連の動作のテストをする際に、
「印刷するクラス」に依存している状態だと、テストをするたびに給料明細を印刷することになってしまい、紙を大量に使用し、社内は深刻な紙不足に陥ります。そんなときにDIの手法を使った実装にしていれば、テスト用の出力クラスを作ることができ、印刷ではなく、PDFをローカルのフォルダに出力することで印刷したことにしてしまえば、テスト時に印刷をする必要は無くなります。(もちろん、本番に移行するときは一度印刷して確かめてくださいね。)
- コードの柔軟性
DIの手法を使ったプログラムの場合、インターフェースに依存している状態になるので、それを継承したプログラムであれば中身がどう変わっても、使う側に変更の必要がなく、将来的な変更や拡張がしやすくなります。
public class Program
{
static void Main(string[] args)
{
IEnergy gasoline = new Gasoline();
// ロボットにガソリンを渡す
Robot robot = new Robot(gasoline);
robot.Start();
}
}
public class Robot
{
private IEnergy _energy;
public Robot(IEnergy energy)
{
_energy = energy;
}
public void Start()
{
// SupplyPowerの処理の中身は変わっているが変更する必要がない
_energy.SupplyPower();
}
}
public class Gasoline : IEnergy
{
// 処理を変更
public void SupplyPower()
{
Console.WriteLine(“必要な燃料の量を計算します”)
Console.WriteLine("ガソリンを注入します");
Console.WriteLine(“注入完了をお知らせします);
}
}
以上が依存関係逆転の原則とDIについてとなります。
具体的な実装に依存するのではなく、インターフェースに依存したつくりを行って、管理、テストしやすいプログラムの実装を目指していきたいと思います。
次は別記事にて、DIの手法を使う際に便利なDIコンテナというツールの使い方について紹介します。