🐧
Goのinterface
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