🐧

Goのinterface

2022/04/15に公開

interfaceとは

  • メソッドの型だけを記述した型のこと。
  • interfaceを通してオブジェクトの振る舞いを定義することができる。

interfaceのメリット

  • 異なる実体を同じように扱える。
  • 疎結合を保ち変更時の修正箇所を減らせる。

interfaceの定義方法

  • interfaceは以下のように定義します。
type 型名 interface {
    メソッド名(引数の型) 返り値の型
}

interfaceの仕様

  • interfaceはあくまでメソッドを定義するだけでその中身が何なのか、
    どのような実装なのかについては一切関知しない。
  • そのため、interfaceを使う際には必ず実体が必要になる。
  • interfaceで定義されたメソッドを全て実装すると、
    特段宣言をせずとも(暗黙的に)そのinterfaceを満たしたものと見なされる。
    *こういうのをダックタイピングとも言う。

interfaceがなぜ必要か?

最初にinterfaceがない簡単な例を用いて説明します。
下記のような実装があるとします。

main.go
package main

import "fmt"

type Dog struct {
    Name string
}

type Human struct {
    Name string
}

func (d Dog) Speak() {
    fmt.Println("わんわん")
}

func (h Human) Speak() {
    fmt.Println("犬に日和ってる奴いる?いねえよなぁ!!?")
}

func main() {
    kuro := Dog{ Name: "Kuro" }
    manjirou := Human{ Name: "Manjirou" }
    
    kuro.Speak()
    manjirou.Speak()
}

実行すると当然ですが以下のように出力されます。

> go run main.go
わんわん
犬に日和ってる奴いる?いねえよなぁ!!?

さて、ここでDogにAttack()という処理を追加することになったとします。

main.go
func main() {
    kuro := Dog{ Name: "Kuro" }
    manjirou := Human{ Name: "Manjirou" }
    
    kuro.Speak()
    manjirou.Speak()
    
    Attack(kuro) // 追加
}

// 追加
func Attack(d Dog){
    fmt.Println("Attack the enemy!")
}

しかし、後から人間にもAttack()という処理を追加することになりました。

main.go
func main() {
    kuro := Dog{ Name: "Kuro" }
    manjirou := Human{ Name: "Manjirou" }
    
    kuro.Speak()
    manjirou.Speak()
    
    Attack1(kuro) // 犬用
    Attack2(manjirou) // 人間用
}

// 名前の衝突を避けるため、Attack1()、Attack2()としています

// 犬用
func Attack1(d Dog){
    fmt.Println("Attack the enemy!")
}

// 人間用
func Attack2(h Human){
    fmt.Println("Attack the enemy!")
}

ここで少し問題が生じます。
見ての通り犬用と人間用でコードがほぼ同じで、とても嫌な気持ちになります。
さらに、今はDogとHumanの2種類しかありませんが、
今後、CatやMonkeyなど新しいtypeが追加され、
これらにもAttack()の処理を追加することになったら、
Cat type用、Monkey type用にAttack()関数を増やす事になり、
とてもめんどくさいです。

ここでinterfaceを使います

type Fight interface {
    Speak()
}

これをざっくり言うと

「話せるやつは全員戦闘タイプである」

みたいな意味になります。

もう少しちゃんと言うと

「Fightという型は必ずSpeak()メソッドをもつことを保証している」

または、

「Speak()メソッドを持っているものは全てFight interfaceである」

みたいな意味になります。

これらを踏まえた上で先ほどのコードを改良すると下記のようになります。

main.go
package main

import "fmt"

type Dog struct {
    Name string
}

type Human struct {
    Name string
}

// interface
type Fight interface {
    Speak()
}

// ここでFight interfaceを満たしている
func (d Dog) Speak() {
    fmt.Println("わんわん")
}

// ここでFight interfaceを満たしている
func (h Human) Speak() {
    fmt.Println("犬に日和ってる奴いる?いねえよなぁ!!?")
}

func main() {
    kuro := Dog{ Name: "Kuro" }
    manjirou := Human{ Name: "Manjirou" }
    
    kuro.Speak()
    manjirou.Speak()
    
    // kuroはFight interfaceを満たしているため、
    // Attackメソッドの引き数になれる
    Attack(kuro) 
    
    // manjirouはFight interfaceを満たしているため、
    // Attackメソッドの引き数になれる
    Attack(manjirou) 
}

// Fight interfaceを引き数にとるAttackメソッド
func Attack(f Fight){
    fmt.Println("Attack the enemy!")
}

ポイントは同じコードで2つの実体を扱うことができている点です。
これはオブジェクト指向におけるポリモーフィズムをイメージすると
分かりやすいと思います。
また、今後、例えば、猫(Cat type)や猿が(Monkey type)が追加されたとしても、
Fight interfaceを満たしている限り、
同じようにAttack()メソッドに放り込むことができます。

Discussion