What is DI?


WPF SheepCalendar 第2日目の記事です。

一般的なデスクトップアプリ開発を通じてDependency Injection(DI:依存性注入)というものを体感すべくCodingを試みました。DIは分離性と結合度を下げ、テスト実装を容易にします。

参考になりそうなとても素晴らしい記事

https://qiita.com/hshimo/items/1136087e1c6e5c5b0d9f


これを実際のプロジェクトで再現します。

依存性注入とはC#において、**Interfaceで実装し、コンストラクタで実装したアルゴリズム（ビジネスロジック）を実装したレイヤー層を注入**という認識でまず間違いないです。他の言語でも多分似たようなものでしょう。

# Github
私はとても初心者に優しいので、あまりにも万年初心者過ぎてムリだよという方向けにGithubリポジトリを作成しました。

出来ればgit Cloneでやろう(Download zipでもいいけど)。
[Gsudo](https://qiita.com/_masa_u/items/a799745d70b30c32a03b)もインストールするとラクです。
```powershell:powerShell
git clone https://github.com/Sheephuman/DependensyTest.git
```

https://github.com/Sheephuman/DependensyTest



# 外観
シンプルにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145714/13bca9e6-4262-e027-41b1-eff0952bb252.png)

```xml:MainWindow.cs
<Window x:Class="DependensyTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DependensyTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <StackPanel Orientation="Vertical" VerticalAlignment="Center">
            <TextBox TextWrapping="Wrap"　Text="SheepBell"　 x:Name="corralsheepText" Width="300" Height="100" Margin="0,0,0,5"/>
            <Button Content="test実行" Click="testExecute_Click" x:Name="testExecute" Width="100" Margin="0,0,0,5"/>
           
            
        </StackPanel>
    </Grid>
</Window>

```



# 実装してみる

まず**テスト実装を注入するためのレイヤークラス**を作成します。
"IDependencyInjectionTests.cs" としました（何でも構いません）
面倒なのでinterfaceもこちらに突っ込みます。

```Csharp:DependencyInjectionTests.cs
using System.Text.RegularExpressions;

namespace IDependensyTest
{
     public interface IDependencyInjectionTests
 {
     public string 羊を追い込む処理(string sheeps);

     public int 羊を追い込んだ数をカウントする(string sheeps);
 }


        public class TestService : IDependencyInjectionTests
        {
     

            public int 羊を追い込んだ数をカウントする(string sheeps)
            {
                string matchPattern = "sheep";
                int count = Regex.Matches(sheeps, matchPattern).Count;

                return count;
            }

            public string 羊を追い込む処理(string sheeps)
            {
                return sheeps.Replace("sheep", "| sheep |");
            }

        }    
}
```

:::note info
Tips
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145714/38f285ab-d556-0818-f7db-d1738d6fd1ce.png)

Visual Studioをリファクタリング機能を使えば自動で実装作業出来ます（VSCodeは知らん）
:::


# 注入する対象のメインクラスを作成する


分かり易さ優先で日本語名のクラスを使用させていただきますが、実際は英語の命名をオススメします。


```csharp:羊を囲い込むクラス.csとインターフェース

namespace DependensyTest
{
    public interface IDependencyInjectionTests
    {
        public string 羊を追い込む処理(string sheeps);

        public int 羊を追い込んだ数をカウントする(string sheeps);
    }


    public class 羊を囲い込むクラス
    {
        public 羊を囲い込むクラス(IDependencyInjectionTests _injection)
        {

            testInjection = _injection;
        }


        private IDependencyInjectionTests testInjection;


        public int tester(string sheeps)
        {
            return testInjection.羊を追い込んだ数をカウントする(sheeps);

        }


    }
}

```



# Buttonイベントの実装

XamlでButtonにクリックイベントを登録します。
```xml
<Button Content="test実行" Click="testExecute_Click" x:Name="testExecute" Width="100" Margin="0,0,0,5"/>          
```
  
## イベント呼び出し
インターフェースは呼び出しは　
　```インターフェース 変数名 = new　クラス名()``` となります。覚えましょう．
日本語名を使用したせいでコード補完が出ません。~~クラス名に日本語名を使用するのはやめましょう。~~

これは**コンストラクタインジェクション**の一例となります。
```csharp

  private void testExecute_Click(object sender, RoutedEventArgs e)
  {
      //interfeaceを介してtest実装レイヤー呼び出し（interface Injection）
      IDependencyInjectionTests inject = new TestService();　

      //テスト実装レイヤーをメイン実装クラスに注入
      var injectionTests = new 羊を囲い込むクラス(inject); 

      int res = injectionTests.tester(corralsheepText.Text);


      MessageBox.Show(res.ToString());
     

  }
```


:::note
構造としてはこのようなイメージです
緑のレイヤーがテスト用実装を示します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145714/72fe5df6-5987-c5a3-ad6d-87bf85ab6ab0.png)

テスト実装をメイン実装に差し込みます。
:::


# この実装のメリット

実装としてはあまり直感的ではなく分かりにくいと思われますが、依存性を失くしテストを容易にするとはどういった意味なのかについて解説していきます。

## 新しい実装を追加してみる

まず、新しいテスト実装用メソッドを書きます
![bandicam 2024-04-11 10-03-53-185_Harua.mp4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145714/0433ffb1-0c66-76af-945f-c3c18760e2a8.gif)


新しいテスト実装を書きます（前の実装はコメントアウトでもした方がいい）。

![bandicam 2024-04-11 10-05-09-583_Harua.mp4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145714/b94308c7-896f-bc15-a254-e887c05899d8.gif)

実装は既存のビジネスロジックを変更しません。そして前の実装との切り替えが自在です。


## 実行結果
こんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145714/3566a278-5795-3e65-c3bb-33025c87288e.png)



## 如何でしたでしょうか？
~~如何でしたか構文~~
このように、実装したメソッドがあるテスト用レイヤーを"差し込む"(Injection)事がこの実装方法の肝です。既存の実装に何の手を加える事なく実装出来ます。
ただ単にinterfaceを実装するというだけではなく、「羊にペイントをするクラス」「蹄のお手入れをするクラス」などのテスト用クラスをテスト用レイヤーに追加することで、いちいち**単体テストプロジェクトを立ち上げたりすることなく**、メインクラスの実装と分離することが出来ます。これは大きなメリットと言えます。

ドキュメント性も上がりますし、**以前の実装に戻したくなった場合**もこの実装方法であれば容易です。

間に**テスト用レイヤーを挟む**のがポイント。

# 別のインジェクションパターンを試す
以下はPHPの記事ですが、CSharpで出来そうだったので試しました。

https://qiita.com/1000k/items/aef6aed46b0fc34cc15e

## InterFaceインジェクション(Interface Injection)

Interfaceで注入するやり方。

### 外観
```xml
<Button Content="test2実行" Click="testExecute2_Click" x:Name="testExecute2" Width="100" Margin="0,0,0,5"/>
```
羊を囲い込むクラス.csに
```csharp:羊を囲い込むクラス.cs
   public void SetDependency(IDependencyInjectionTests dependency)
   {        

       testInjection = dependency;
   }
```
を追加します。

## 呼び出し
```csharp
   private void testExecute2_Click(object sender, RoutedEventArgs e)
   {
    //interfaceを介してクラス読み込み
 IDependencyInjectionTests interLayer = new TestService();

 //通常のインスタンス作成
 羊を囲い込むクラス injector = new 羊を囲い込むクラス();

 //メソッドの引数でInterfaceLayerをインジェクション
 injector.SetDependency(interLayer);

 var res = injector.tester2(corralsheepText.Text);
 MessageBox.Show(res.ToString());
   }
```


## 実行結果
![bandicam 2024-04-15 02-30-30-252_Harua.mp4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145714/eaf6ff66-de80-509f-c702-a8f8ac103165.gif)

# Setter Injection
ここまで来たら割と単純です。

## プロパティを実装クラスに追加する
```csharp:羊を囲い込むクラス.cs


     private IDependencyInjectionTests? testInjection;
     
     public IDependencyInjectionTests IDProperty
     {
         get
         { if (testInjection == null) throw new InvalidOperationException();
             return testInjection;
         }
         set
         {
             testInjection = value;
         }
     }
```

## 呼び出し側
```csharp


  private void testExecute3_Click(object sender, RoutedEventArgs e)
  {
     //interfaceを介してクラス読み込み
 IDependencyInjectionTests interLayer = new TestService();
 //通常のインスタンス作成
 羊を囲い込むクラス injector = new 羊を囲い込むクラス();

 injector.IDProperty = interLayer;

 var res = injector.tester2(corralsheepText.Text);
 MessageBox.Show(res.ToString());
  }
```

実行結果は割愛。

:::note info
試しにコメントアウトしてみた
```csharp
  private void testExecute3_Click(object sender, RoutedEventArgs e)
  {
     //interfaceを介してクラス読み込み
 IDependencyInjectionTests interLayer = new TestService();
 //通常のインスタンス作成
 羊を囲い込むクラス injector = new 羊を囲い込むクラス();

 //injector.IDProperty = interLayer;

 var res = injector.tester2(corralsheepText.Text);
 MessageBox.Show(res.ToString());
  } 
```
結果　
testInjectionはNullとなり、Codeが正しく動作している事が確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145714/f2ebfa04-4bda-9770-5b73-22bb901a1ace.png)


:::



# 参考になりましたらいいねをお願いします！
たいへん励みになります(とても丁寧な記事)。

出典:ポプテピピック（お気に触りましたら申し訳ない）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145714/b1c0afd2-a77e-3ef1-e335-200eb109d330.png)