VB のたまご

作成日: 2017/05/14, 更新日: 2017/05/14



DIコンテナ

  •  一般的に、「1つのクラスだけで要件を満たせるようなプログラム」は滅多に無く、 「複数のクラスを連携させて解決するような作りのプログラム」になることが多いかと思います。 つまり、何らかのクラスは、何らかのクラスに依存しています。

  •  例えば、クラスのインスタンス生成時(=New コンストラクタ時)、メソッド呼び出し時、公開メンバーへの値セット時など、 外部から何らかの値を受け取って、処理をおこなうことが多いかと思います。 先程のサンプルでは、メソッド呼び出し時に、外部から処理クラスを受け取っていました。

  • スポンサーリンク


  •  これらの DI を手動でおこなうのではなく、自動で DI してくれるものが DI コンテナです。 DI コンテナは、インスタンス生成器のようなものです。

  •  とりあえずサンプルを見てどういうものか見てみましょう。 以下では、DI コンテナの1つである Unity コンテナを使っています。 名前空間は、Microsoft.Practices.Unity.UnityContainer です。

  •  最初に、Unity コンテナをインスタンス生成します。
  • Dim container = New UnityContainer
    

  •  インスタンスを受け取る場合は、Resolve メソッドを呼び出して使います。 引数無しコンストラクタを持つクラスであれば、未登録でもインスタンス生成してくれます。 ※何らかのルールや、何らかの値を必要とする場合は、あらかじめ、インスタンス生成ルールを登録しておきます。
  • Class Class1
    
        Public Sub New()
        End Sub
    
        Public Sub Hello()
            Console.WriteLine("Class1.Hello")
        End Sub
    
    End Class
    
    Dim obj1 = container.Resolve(Of Class1)
    obj1.Hello()
    ' Class1.Hello
    

  •  以下のように、インターフェースに対して継承先クラスのインスタンスを生成するように登録すると、 インターフェース指定することで、継承先クラスのインスタンスを受け取ることができます。 DI コンテナに登録する場合は、RegisterType メソッドを使います。
  • Interface IClass2
        Sub Hello()
    End Interface
    
    Class Class2
        Implements IClass2
    
        Public Sub Hello() Implements IClass2.Hello
            Console.WriteLine("Class2.Hello")
        End Sub
    
    End Class
    
    container.RegisterType(Of IClass2, Class2)()
    Dim obj2 = container.Resolve(Of IClass2)
    obj2.Hello()
    ' Class2.Hello
    

  •  DI には3種類のインジェクション方法があります。 コンストラクタ・インジェクション、セッター・インジェクション、インターフェース・インジェクションです。 1つ1つ見ていきましょう。

  •  以下では、コンストラクタ・インジェクションを使っています。 コンストラクタが3つありますので、クラスの型の指名以外に、名前付けをして一意を判断しています。
  • Class Class3
    
        Private Int As Integer = 0
        Private Str As String = "default"
    
        Public Sub New()
        End Sub
    
        Public Sub New(i As Integer)
            Me.Int = i
        End Sub
    
        Public Sub New(i As Integer, s As String)
            Me.Int = i
            Me.Str = s
        End Sub
    
        Public Sub Hello()
            Console.WriteLine($"Int = {Me.Int}, Str = {Me.Str}")
        End Sub
    
    End Class
    
    container.RegisterType(Of Class3)("zero", New InjectionConstructor())
    container.RegisterType(Of Class3)("one", New InjectionConstructor(10))
    container.RegisterType(Of Class3)("two", New InjectionConstructor(20, "Hello"))
    
    Dim obj3a = container.Resolve(Of Class3)("zero")
    Dim obj3b = container.Resolve(Of Class3)("one")
    Dim obj3c = container.Resolve(Of Class3)("two")
    
    obj3a.Hello()
    obj3b.Hello()
    obj3c.Hello()
    ' Int = 0, Str = default
    ' Int = 10, Str = default
    ' Int = 20, Str = Hello
    

  •  コンストラクタの初期値は、取得時に変更することもできます。 この時、セット先のプロパティ名ではなく、コンストラクタの引数名を指定することに注意してください。 私は間違えてしまい、ハマってしまいましたorz。
  • Dim obj3d = container.Resolve(Of Class3)("two", New ParameterOverride("i", 99), New ParameterOverride("s", "World"))
    obj3d.Hello()
    ' Int = 99, Str = World
    

  •  2つ目のコンストラクタ・インジェクションの例です。今度は、プリミティブ型ではなくデータクラス型を渡します。 引数に渡すクラスのインスタンスは Unity コンテナ任せです。
  • Interface ILanguage
        Sub Hello()
    End Interface
    
    Class Japan
        Implements ILanguage
    
        Public Sub Hello() Implements ILanguage.Hello
            Console.WriteLine("こんにちは")
        End Sub
    End Class
    
    Class English
        Implements ILanguage
    
        Public Sub Hello() Implements ILanguage.Hello
            Console.WriteLine("Hello")
        End Sub
    End Class
    
    Class Class4
    
        Private Language As ILanguage
    
        Public Sub New(_Language As ILanguage)
            Me.Language = _Language
        End Sub
    
        Public Sub Hello()
            Me.Language.Hello()
        End Sub
    
    End Class
    
    container.RegisterType(Of Class4)("japan", New InjectionConstructor(container.Resolve(Of Japan)))
    container.RegisterType(Of Class4)("english", New InjectionConstructor(container.Resolve(Of English)))
    
    Dim obj4a = container.Resolve(Of Class4)("japan")
    Dim obj4b = container.Resolve(Of Class4)("english")
    
    obj4a.Hello()
    obj4b.Hello()
    ' こんにちは
    ' Hello
    

  •  続いて、セッター・インジェクションの例です。
  • Class Class5
    
        Public Property Int As Integer
        Public Property Str As String
    
    End Class
    
    container.RegisterType(Of Class5)(New InjectionProperty("Int", 30), New InjectionProperty("Str", "Hello World"))
    Dim obj5a = container.Resolve(Of Class5)
    Console.WriteLine($"Int = {obj5a.Int}, Str = {obj5a.Str}")
    ' Int = 30, Str = Hello World
    

  •  セッター・インジェクションも、取得時に初期値の変更ができます。
  • Dim obj5b = container.Resolve(Of Class5)(New PropertyOverride("Int", 60), New PropertyOverride("Str", "Good morning"))
    Console.WriteLine($"Int = {obj5b.Int}, Str = {obj5b.Str}")
    ' Int = 60, Str = Good morning
    

  •  続いて、インターフェース・インジェクションの例です。 以下の例だと、どちらかというと、メソッド・インジェクションに、インターフェースを渡すような感じですね。 この場合、【インスタンスを受け取って、メソッド呼び出し】ではなく、【インスタンスを返却するときに自動実行される】ところに注意してください。
  • Class Class6
    
        Public Sub Hello(language As ILanguage)
            language.Hello()
        End Sub
    
    End Class
    
    container.RegisterType(Of Class6)("japan", New InjectionMethod("Hello", container.Resolve(Of Japan)))
    container.RegisterType(Of Class6)("english", New InjectionMethod("Hello", container.Resolve(Of English)))
    Dim obj6a = container.Resolve(Of Class6)("japan")
    Dim obj6b = container.Resolve(Of Class6)("english")
    ' こんにちは
    ' Hello
    

  •  続いては、Dependency 属性によるインジェクションの例です。 クラスメンバーに、Dependency 属性を付けると、そのクラスのインスタンス生成の他、そのメンバーのインスタンスも生成してくれます。
  • Class Class7a
    
        Public Property Int As Integer
    
        Public Sub New()
            Me.Int = 45
        End Sub
    
    End Class
    
    Class Class7b
    
        <Dependency>
        Public Property Obj As Class7a
    
    End Class
    
    Dim obj7a = container.Resolve(Of Class7b)
    Dim obj7b = New Class7b
    
    ' デバッグで中身を見ると、
    ' obj7a.Obj プロパティは、インスタンスがある
    ' obj7b.Obj プロパティは、Nothing
    

  •  インスタンスの生成を独自に指定することもできます。
  • Class Class8
    
        Public Property Int As Integer
        Public Property Str As String
    
    End Class
    
    container.RegisterType(Of Class8)(New InjectionFactory(Function(x) New Class8 With {.Int = 9999, .Str = "Good afternoon"}))
    Dim obj8 = container.Resolve(Of Class8)
    Console.WriteLine($"Int = {obj8.Int}, Str = {obj8.Str}")
    ' Int = 9999, Str = Good afternoon
    

    スポンサーリンク


  •  ここまでは、都度インスタンス生成したデータを受け取っていましたが、シングルトン形式で扱うこともできます。 クラスのインスタンスをシングルトンで扱いたい場合は、登録時に、ContainerControlledLifetimeManager クラスのインスタンスを渡します。
  • Class Class9a
    
        Private _GeneratedTime As DateTime
        Public ReadOnly Property GeneratedTime As DateTime
            Get
                Return Me._GeneratedTime
            End Get
        End Property
    
        Public Sub New()
            Me._GeneratedTime = DateTime.Now
        End Sub
    
    End Class
    
    container.RegisterType(Of Class9a)(New ContainerControlledLifetimeManager)
    Dim obj9a = container.Resolve(Of Class9a)
    
    System.Threading.Thread.Sleep(1000)
    Dim obj9b = container.Resolve(Of Class9a)
    
    If obj9a.GeneratedTime.CompareTo(obj9b.GeneratedTime) = 0 Then
        Console.WriteLine("同じ")
    Else
        Console.WriteLine("違う")
    End If
    ' 同じ
    

  •  シングルトン形式のその2です。
  • Class Class9b
    
        Public Property Int As Integer
        Public Property Str As String
    
    End Class
    
    container.RegisterType(Of Class9b)(New ContainerControlledLifetimeManager)
    Dim obj9c = container.Resolve(Of Class9b)
    
    obj9c.Int = 8888
    obj9c.Str = "sss"
    
    Dim obj9d = container.Resolve(Of Class9b)
    If (obj9c.Int = obj9d.Int) AndAlso (obj9c.Str = obj9d.Str) Then
        Console.WriteLine("同じ")
    Else
        Console.WriteLine("違う")
    End If
    ' 同じ
    
    obj9c.Int = 777
    obj9c.Str = "rrr"
    
    If (obj9c.Int = obj9d.Int) AndAlso (obj9c.Str = obj9d.Str) Then
        Console.WriteLine("同じ")
    Else
        Console.WriteLine("違う")
    End If
    ' 同じ
    

  •  最後に、前回のクラスを、Unity コンテナを利用した場合のサンプルです。 パッと見、Unity コンテナのために、登録作業と利用作業が増えたせいで分かりづらくなっただけじゃん、という印象を持ってしまいますが、 今後、Prism 内では、Unity コンテナを使っていきますので、今は、感想はちょっと置いておいて、使い方だけ覚えてください。
  • 'Dim myMeeting = New Meeting
    'myMeeting.Speak(New JapaneseSpeaker)
    'myMeeting.Speak(New AmericanSpeaker)
    
    ' 登録
    Dim im As InjectionMethod
    im = New InjectionMethod("Speak", container.Resolve(Of JapaneseSpeaker))
    container.RegisterType(Of Meeting)("japan", im)
    
    im = New InjectionMethod("Speak", container.Resolve(Of AmericanSpeaker))
    container.RegisterType(Of Meeting)("america", im)
    
    ' 生成したインスタンスを返却(と同時に Speak メソッドを自動実行)
    container.Resolve(Of Meeting)("japan")
    container.Resolve(Of Meeting)("america")
    ' こんにちは!
    ' Hello!
    

  •  最後に、ソース全体です。

  • Imports Microsoft.Practices.Unity
    
    Module Module1
    
        Sub Main()
    
            ' UnityContainer を使う
            ' 型の登録方法は、Xxx という型の場合、Yyy というインスタンス生成をしてくださいという風に、生成方法を登録する
            ' 引数無しのインスタンス生成で良い場合は、登録不要でインスタンス生成できる
            Dim container = New UnityContainer
    
            ' 未登録でインスタンス生成
            Dim obj1 = container.Resolve(Of Class1)
            obj1.Hello()
    
            ' 指定インターフェースに対して、指定の継承先のクラスのインスタンスを生成するように登録
            container.RegisterType(Of IClass2, Class2)()
            Dim obj2 = container.Resolve(Of IClass2)
            obj2.Hello()
    
            ' DI には3種類のインジェクション方法がある
            '  コンストラクタ・インジェクション
            '  (プロパティの)セッター・インジェクション
            '  インターフェース・インジェクション
    
            ' コンストラクタ・インジェクション1
            ' 1つの型に対して、複数のインスタンス生成方法がある場合、さらに名前付けして分類する
            container.RegisterType(Of Class3)("zero", New InjectionConstructor())
            container.RegisterType(Of Class3)("one", New InjectionConstructor(10))
            container.RegisterType(Of Class3)("two", New InjectionConstructor(20, "Hello"))
    
            Dim obj3a = container.Resolve(Of Class3)("zero")
            Dim obj3b = container.Resolve(Of Class3)("one")
            Dim obj3c = container.Resolve(Of Class3)("two")
    
            obj3a.Hello()
            obj3b.Hello()
            obj3c.Hello()
    
            ' コンストラクタの初期値変更
            ' この時、セット先のプロパティ名ではなく、コンストラクタの引数名を指定するので注意
            Dim obj3d = container.Resolve(Of Class3)("two", New ParameterOverride("i", 99), New ParameterOverride("s", "World"))
            obj3d.Hello()
    
    
    
            ' コンストラクタ・インジェクション2
            container.RegisterType(Of Class4)("japan", New InjectionConstructor(container.Resolve(Of Japan)))
            container.RegisterType(Of Class4)("english", New InjectionConstructor(container.Resolve(Of English)))
    
            Dim obj4a = container.Resolve(Of Class4)("japan")
            Dim obj4b = container.Resolve(Of Class4)("english")
    
            obj4a.Hello()
            obj4b.Hello()
    
    
    
            ' (プロパティの)セッター・インジェクション
            container.RegisterType(Of Class5)(New InjectionProperty("Int", 30), New InjectionProperty("Str", "Hello World"))
            Dim obj5a = container.Resolve(Of Class5)
            Console.WriteLine($"Int = {obj5a.Int}, Str = {obj5a.Str}")
    
            ' プロパティ初期値の変更
            Dim obj5b = container.Resolve(Of Class5)(New PropertyOverride("Int", 60), New PropertyOverride("Str", "Good morning"))
            Console.WriteLine($"Int = {obj5b.Int}, Str = {obj5b.Str}")
    
    
    
            ' インターフェース・インジェクション
            ' メソッドの引数にインターフェースを継承したクラスのインスタンスをインジェクションする
            ' 【インスタンスを受け取って、メソッド呼び出し】ではなく、【インスタンスを返却するときに自動実行される】
            container.RegisterType(Of Class6)("japan", New InjectionMethod("Hello", container.Resolve(Of Japan)))
            container.RegisterType(Of Class6)("english", New InjectionMethod("Hello", container.Resolve(Of English)))
            Dim obj6a = container.Resolve(Of Class6)("japan")
            Dim obj6b = container.Resolve(Of Class6)("english")
    
    
    
            ' Dependency 属性によるインジェクション
            ' Class7b のインスタンス生成の他、Dependency 属性が付いているメンバーにも、インスタンス生成してくれる
            Dim obj7a = container.Resolve(Of Class7b)
            Dim obj7b = New Class7b
    
            ' obj7a.Obj プロパティは、インスタンスがある
            ' obj7b.Obj プロパティは、Nothing
            Console.WriteLine("")
    
    
    
            ' 独自インスタンス生成の指定
            container.RegisterType(Of Class8)(New InjectionFactory(Function(x) New Class8 With {.Int = 9999, .Str = "Good afternoon"}))
            Dim obj8 = container.Resolve(Of Class8)
            Console.WriteLine($"Int = {obj8.Int}, Str = {obj8.Str}")
    
    
    
            ' インスタンスクラスの生存期間
            ' 特に指定しない場合は、都度インスタンス生成する
            ' ContainerControlledLifetimeManager クラスを渡すことでシングルトンにできる
            container.RegisterType(Of Class9a)(New ContainerControlledLifetimeManager)
            Dim obj9a = container.Resolve(Of Class9a)
    
            System.Threading.Thread.Sleep(1000)
            Dim obj9b = container.Resolve(Of Class9a)
    
            If obj9a.GeneratedTime.CompareTo(obj9b.GeneratedTime) = 0 Then
                Console.WriteLine("同じ")
            Else
                Console.WriteLine("違う")
            End If
    
            ' テスト2
            container.RegisterType(Of Class9b)(New ContainerControlledLifetimeManager)
            Dim obj9c = container.Resolve(Of Class9b)
    
            obj9c.Int = 8888
            obj9c.Str = "sss"
    
            Dim obj9d = container.Resolve(Of Class9b)
            If (obj9c.Int = obj9d.Int) AndAlso (obj9c.Str = obj9d.Str) Then
                Console.WriteLine("同じ")
            Else
                Console.WriteLine("違う")
            End If
    
            obj9c.Int = 777
            obj9c.Str = "rrr"
    
            If (obj9c.Int = obj9d.Int) AndAlso (obj9c.Str = obj9d.Str) Then
                Console.WriteLine("同じ")
            Else
                Console.WriteLine("違う")
            End If
    
    
    
            ' 前述のサンプルの場合、インターフェース・インジェクションによる DI をしてインスタンス生成&メソッド実行する
            ' 準備
            Dim im As InjectionMethod
            im = New InjectionMethod("Speak", container.Resolve(Of JapaneseSpeaker))
            container.RegisterType(Of Meeting)("japan", im)
    
            im = New InjectionMethod("Speak", container.Resolve(Of AmericanSpeaker))
            container.RegisterType(Of Meeting)("america", im)
    
            ' 生成したインスタンスを返却(と同時に Speak メソッドを自動実行)
            container.Resolve(Of Meeting)("japan")
            container.Resolve(Of Meeting)("america")
    
    
    
            Console.Read()
        End Sub
    
        ' テスト1
        Class Class1
    
            Public Sub New()
            End Sub
    
            Public Sub Hello()
                Console.WriteLine("Class1.Hello")
            End Sub
    
        End Class
    
        ' テスト2
        Interface IClass2
            Sub Hello()
        End Interface
    
        Class Class2
            Implements IClass2
    
            Public Sub Hello() Implements IClass2.Hello
                Console.WriteLine("Class2.Hello")
            End Sub
    
        End Class
    
        ' コンストラクタ・インジェクション用1
        Class Class3
    
            Private Int As Integer = 0
            Private Str As String = "default"
    
            Public Sub New()
            End Sub
    
            Public Sub New(i As Integer)
                Me.Int = i
            End Sub
    
            Public Sub New(i As Integer, s As String)
                Me.Int = i
                Me.Str = s
            End Sub
    
            Public Sub Hello()
                Console.WriteLine($"Int = {Me.Int}, Str = {Me.Str}")
            End Sub
    
        End Class
    
        ' コンストラクタ・インジェクション用2
        Interface ILanguage
            Sub Hello()
        End Interface
    
        Class Japan
            Implements ILanguage
    
            Public Sub Hello() Implements ILanguage.Hello
                Console.WriteLine("こんにちは")
            End Sub
        End Class
    
        Class English
            Implements ILanguage
    
            Public Sub Hello() Implements ILanguage.Hello
                Console.WriteLine("Hello")
            End Sub
        End Class
    
        Class Class4
    
            Private Language As ILanguage
    
            Public Sub New(_Language As ILanguage)
                Me.Language = _Language
            End Sub
    
            Public Sub Hello()
                Me.Language.Hello()
            End Sub
    
        End Class
    
        ' (プロパティの)セッター・インジェクション用
        Class Class5
    
            Public Property Int As Integer
            Public Property Str As String
    
        End Class
    
        ' インターフェース・インジェクション用
        Class Class6
    
            Public Sub Hello(language As ILanguage)
                language.Hello()
            End Sub
    
        End Class
    
        ' Dependency 属性用
        Class Class7a
    
            Public Property Int As Integer
    
            Public Sub New()
                Me.Int = 45
            End Sub
    
        End Class
    
        Class Class7b
    
            <Dependency>
            Public Property Obj As Class7a
    
        End Class
    
        ' 独自インスタンス生成用
        Class Class8
    
            Public Property Int As Integer
            Public Property Str As String
    
        End Class
    
        ' シングルトン用1
        Class Class9a
    
            Private _GeneratedTime As DateTime
            Public ReadOnly Property GeneratedTime As DateTime
                Get
                    Return Me._GeneratedTime
                End Get
            End Property
    
            Public Sub New()
                Me._GeneratedTime = DateTime.Now
            End Sub
    
        End Class
    
        ' シングルトン用2
        Class Class9b
    
            Public Property Int As Integer
            Public Property Str As String
    
        End Class
    
    End Module