例えて説明
部屋と照明でたとえて依存性の注入
をしない場合とする場合をコードで説明する。
依存性の注入(DI)をしない場合
初期構成
- 照明
- 白熱電球はフィラメントが光る機能がある。
type Incandescent struct{}
func (*Incandescent) LightUp() string {
return "フィラメントが光るよ!"
}
- 部屋の構成
- 部屋には照明(白熱電球)が2つあって、照明に電源を入れる機能をするスイッチも2つある。
type Room struct {
LightOne Incandescent
LightTwo Incandescent
}
func (r *Room) SwitchOnOne() {
fmt.Println("1番照明:", r.LightOne.LightUp())
}
func (r *Room) SwitchOnTwo() {
fmt.Println("2番照明:", r.LightTwo.LightUp())
}
僕の部屋を生成する
Room
構造体で僕の部屋を作って電気をつける
func main() {
myRoom := new(Room)
myRoom.SwitchOnOne() // 1番照明: フィラメントが光るよ!
myRoom.SwitchOnTwo() // 2番照明: フィラメントが光るよ!
}
電球を変えたくなった!
時代が進んで、LEDが発売され、一個買って使おうとした。
// LED電球
type LedLight struct{}
func (*LedLight) LightUp() string {
return "LEDが光るよ!"
}
// 部屋の工事が発生 (設計変更)
type Room struct {
LightOne Incandescent
LightTwo LedLight // Incandescentから LedLightに変更
}
問題点
- 僕の部屋は電球の種類に依存していた。
- 部屋は初期に白熱電球だけを考えて設計された。
- 電球の仕組みが異なる照明に変えようとする度に部屋の工事が発生する。(ソケットは一緒なのに!!)
めったに工事がいらない部屋が、明るくするという部屋の一部機能に振り回されるのはおかしくない?
依存性の注入(DI)をする場合
照明の実装は上と変わらない。
照明のソケット(インタフェース)を用意する
部屋の設計に特定種類の電球を付けるのではなく、電球を入れられるソケットを付けて置く。
LigthUp()
という機能さえ満たせば部屋のソケットに入れることができる。
依存性逆転の原則(Dependency Inversion Principle)の依存性逆転パターン - Wikipedia
// 照明のソケット
type LightSocket interface {
LightUp() string
}
type Room struct {
LightOne LightSocket // 部屋にはソケットを付けておく
LightTwo LightSocket
}
// Constructor Injection
func NewRoom(lightOne, lightTwo LightSocket) *Room {
room := &Room{
LightOne: lightOne,
LightTwo: lightTwo,
}
return room
}
部屋に電球を依存性の注入(DI)で入れる
これで、部屋の工事が発生せず、ソケットに合う照明であれば、入れ替えるのが可能になる。
func main() {
lightOne := new(light.Incandescent) // 注入するオブジェクト
lightTwo := new(light.LedLight) // 注入するオブジェクト
myRoom := NewRoom(lightOne, lightTwo) // ここがDI
myRoom.SwitchOnOne() // 1番照明: フィラメントが光るよ!
myRoom.SwitchOnTwo() // 2番照明: LEDが光るよ!
}
DIの利点
テストコードのために関連機能全てを実装せずに、Mock
を使ってテストができる。
上記の「依存性の注入(DI)をしない場合」のコードでは部屋のスイッチを入れると電気が流れるのかをテストしたくても電球の機能が実装されてないとテストが難しい。
DIをするコードではまだ電球の実装有無にかかわらず、このようなMock
電球を用意して注入することでテストが可能
type MockLight struct{}
func (*MockLight) LightUp() string {
return "ぴーぴー音を鳴らす"
}
func TestSwitchOnOne(t *testing.T) {
room := NewRoom(new(MockLight), new(MockLight)) // ここがDI
...
}