GoのInterfaceについて

インターフェースとは
ある機能を持っていることを保証する宣言
→「中身に関係なく、インターフェースで定義された関数を持っていればOK」という抽象的な型
インターフェースの必要性
◇ソフトウェア柔軟性のため
- 別の実装に切り替えることが簡単
- 実装を隠蔽して、使う側から見たらシンプルになる
- テストでモックが使用可能になる
→拡張性、保守性、テスト容易性が確保される
クラスとの関係性
インターフェースは「クラスに対してこういう機能を持て!!」という約束事をする存在
- クラス:データとふるまい(メソッド)を持つ具体的な実装
- インターフェース:ある振る舞いを定義した抽象的な型(中身はもたない)

GolangでのInterface実装
package main
import "fmt"
type Eater interface {
Putin() //口に入れる
Chew() //噛む
}
type Human struct {
Height int
}
type Dog struct {
Kind string
}
// 人間用のインターフェースを実装
func (h Human) Putin() {
fmt.Println("箸で食べる")
}
func (h Human) Chew() {
fmt.Println("歯でしっかり噛む")
}
// 犬用のインターフェースを実装
func (d Dog) Putin() {
fmt.Println("床からそのまま食べる")
}
func (d Dog) Chew() {
fmt.Println("人間と同じように歯で食べる")
}
// インターフェースが引数になる、食べるメソッド
func EatAll(e Eater) {
e.Putin()
e.Chew()
}
func main() {
man := Human{Height: 170}
hina := Dog{Kind: "Cavaria"}
fmt.Println("<人間が食べる>")
EatAll(man)
fmt.Println("<犬が食べる>")
EatAll(hina)
}
上記はGolangでInterfaceを実装した例
Golangでは暗黙的にInterfaceを実装できる。
つまり
func (h Human) Putin() {
fmt.Println("箸で食べる")
}
func (h Human) Chew() {
fmt.Println("歯でしっかり噛む")
}
この二つの関数が実装された時点で
Human構造体はEaterインターフェースを実装されるとGoのコンパイラは判断をする。
ポイント
Interfaceはあくまで型であり、振る舞いの定義をしているだけ。
→**「Interfaceを引数にとる関数を定義して使えるようにする」必要がある**
// インターフェースが引数になる、食べるメソッド
func EatAll(e Eater) {
e.Putin()
e.Chew()
}

Interfaceを利用しない場合
package main
import "fmt"
type Human struct {
Height int
}
type Dog struct {
Kind string
}
// 人間用のメソッド
func (h Human) Putin() {
fmt.Println("箸で食べる")
}
func (h Human) Chew() {
fmt.Println("歯でしっかり噛む")
}
// 犬用のメソッド
func (d Dog) Putin() {
fmt.Println("床からそのまま食べる")
}
func (d Dog) Chew() {
fmt.Println("人間と同じように歯で食べる")
}
// インターフェースなしで、各種の処理を個別に書く
func EatHuman(h Human) {
h.Putin()
h.Chew()
}
func EatDog(d Dog) {
d.Putin()
d.Chew()
}
func main() {
var man Human = Human{Height: 170}
var hina Dog = Dog{Kind: "Cavaria"}
fmt.Println("<人間が食べる>")
EatHuman(man)
fmt.Println("<犬が食べる>")
EatDog(hina)
}
上記のようになる
変更箇所としては以下のようだ
// インターフェースなしで、各種の処理を個別に書く
func EatHuman(h Human) {
h.Putin()
h.Chew()
}
func EatDog(d Dog) {
d.Putin()
d.Chew()
}
Interfaceが定義されていないことによって、犬が食べる振る舞いと、人間が食べる振る舞いを別物として扱わなければいけない!!
→インターフェースを使うことで
「誰が食べるか」ではなく「どう食べるか」に着目して処理の共通化ができる

JavaでのInterface実装
public interface Eater {
void putIn(); // 食べ物を口に入れる
void chew(); // 噛む
}
public class Human implements Eater {
@Override
public void putIn() {
System.out.println("箸で食べる");
}
@Override
public void chew() {
System.out.println("歯でしっかり噛む");
}
}
public class Dog implements Eater {
@Override
public void putIn() {
System.out.println("床からそのまま食べる");
}
@Override
public void chew() {
System.out.println("人間と同じように歯で食べる");
}
}
public class EatingService {
public static void letEat(Eater e) {
e.putIn();
e.chew();
}
}
public class Main {
public static void main(String[] args) {
Eater man = new Human();
Eater dog = new Dog();
System.out.println("<人間が食べる>");
EatingService.letEat(man);
System.out.println("<犬が食べる>");
EatingService.letEat(dog);
}
}

Interface注意点
Interfaceで定義されている関数をその構造体(クラス)がすべて実装していても、実際にその関数を使う必要はない
Golangの場合
例えば
type Eater interface {
PutIn()
Chew()
Swallow()
}
type Human struct{}
func (Human) PutIn() { fmt.Println("箸で食べる") }
func (Human) Chew() { fmt.Println("しっかり噛む") }
func (Human) Swallow(){ fmt.Println("飲み込む") }
func LetEat(e Eater) {
e.PutIn()
}
疑問
「あれ? Chew() や Swallow() を使ってないのに、なぜそれも実装しないといけないの?」
✅ これは「設計の視点の違い」
構造体側:インターフェースを満たす= 契約を守る責任がある
よって、定義されたすべてのメソッドを持たなければならない。
関数側:インターフェースに含まれるメソッドのうち、必要なものだけを使えばよい。
→ 未使用なメソッドがあっても問題ではない。
Javaでも同様
public interface Eater {
void putIn();
void chew();
void swallow();
}
public class Human implements Eater {
@Override
public void putIn() {
System.out.println("箸で食べる");
}
@Override
public void chew() {
System.out.println("しっかり噛む");
}
@Override
public void swallow() {
System.out.println("飲み込む");
}
}
public class EatingService {
public static void letPutIn(Eater e) {
e.putIn(); // chew() や swallow() は使ってない
}
}

ポリモーフィズムとは
同じインターフェース(共通の型)を使って、複数の異なる実装を同じように扱えること
→ある関数が異なる構造体を引数にとっても、同じように動く仕組み
type Eater interface {
Putin() //口に入れる
}
type Human struct {}
type Dog struct {}
// 人間用のインターフェースを実装
func (h Human) Putin() {
fmt.Println("箸で食べる")
}
// 犬用のインターフェースを実装
func (d Dog) Putin() {
fmt.Println("床からそのまま食べる")
}
// インターフェースが引数になる、食べるメソッド
func EatAll(e Eater) {
e.Putin() //←これがポリモーフィズム!!
}
func main(){
EatAll(Human{}) //箸で食べる
EatAll(Dog{}) //床からそのまま食べる
}
- EatAll関数はPutInインターフェースしか知らない
- HumanもDogもPutInインターフェースを満たしている
- 「違う型」でも「同じように」扱える=ポリモーフィズム