見出し画像

Golang_インターフェースとは #468

クラスという概念がないGolangにおいて、インターフェースによってクラスに近いことが実現できます。インターフェースを語るにおいて、Golangにおける「メソッド」について先に軽く触れておきます。

Golangのメソッドとは

メソッドとは特定の型に紐づいた関数を指します。構造体や任意のカスタム型にメソッドを定義できます。

例えば構造体ManShowAgeメソッドを関連付けてみます。

type Man struct {
	Name string
	Age int
}
 
func (m Man) ShowAge() string {
	return strconv.Itoa(m.Age) + "歳"
}

funcの隣にある「(m Man)」はレシーバと呼ばれ、これによってメソッドが型に関連づけられています。上記の例は値レシーバで、構造体のコピーが渡されます。

メソッドから構造体の値を変更したい場合は、以下のようなポインタレシーバを使います。

type Man struct {
	Name string
	Age int
}

func (m *Man) ChangeName(n string) {
	m.Name = n
}
 
func (m *Man) ShowAge() string {
	return strconv.Itoa(m.Age) + "歳"
}

値レシーバとポインタレシーバは、同じ型に紐づくレシーバで統一しておいたほうが良いと言われています(そのためShowAge()もポインタレシーバに直しています)。

また、型に具体的な値が無い状態でメソッドが呼び出された場合、レシーバにはその型のゼロ値が渡されます(nilエラーみたいにはならない)。ただしこれだと処理によってはパニックが発生するので、メソッドの最初にnilチェック等を入れるのが一般的です。

特に構造体のゼロ値はnilなので、パニック回避のために以下のようにnilチェックをいれます。

type Man struct {
	Name string
	Age int
}

func (m *Man) ChangeName(n string) {
    if m == nil {
        fmt.Print("Man is nil!!")
        return
    }
	m.Name = n
}
 
func (m *Man) ShowAge() string {
    if m == nil {
        fmt.Print("Man is nil!!")
        return
    }
	return strconv.Itoa(m.Age) + "歳"
}

Golangのインターフェースの定義

さて本題です。

インターフェースはメソッドの集合を定義する型です。特定のメソッドを持つ任意の型は、そのインターフェースを実装していると見なされます。例えば以下のようなインターフェースがあった場合、

type I interface {
	M()
}

M()を持つ型はインターフェースIを実装していると見なされます。例えば以下の型TはインターフェースIを実装していると見なされます。

type T struct {
	S string
}

func (t *T) M() {
	if t == nil {
		fmt.Println("<nil>")
		return
	}
	fmt.Println(t.S)
}

TM()以外のメソッドを持っていても、インターフェースIを実装していると見なされます。

type T struct {
	S string
}

func (t *T) M() {
	if t == nil {
		fmt.Println("<nil>")
		return
	}
	fmt.Println(t.S)
}
 
func (t *T) M2() {
	if t == nil {
		fmt.Println("<nil>")
		return
	}
    fmt.Println(t.S, 2)
}

インターフェースのコード例

もう少し具体的なコード例を書いてみます。

package main

import (
	"fmt"
	"strconv"
)

type Z interface {
	ChangeName(string)
	ShowAge() string
}


type Man struct {
	Name string
	Age int
}

func (m *Man) ChangeName(n string) {
    if m == nil {
        fmt.Print("Man is nil!!")
        return
    }
	m.Name = n
}

func (m *Man) ShowAge() string {
    if m == nil {
        fmt.Print("Man is nil!!")
        return
    }
	return strconv.Itoa(m.Age) + "歳"
}

func (m *Man) IncrementAge() {
    if m == nil {
        fmt.Print("Man is nil!!")
        return
    }
	m.Age++
}


func main() {
	var z Z
	z = &Man{Name: "John", Age: 30}
	fmt.Println(z)  // 出力: &{John 30}
	z.ChangeName("Alice")
	fmt.Println(z)  // 出力: &{Alice 30}
	fmt.Println(z.ShowAge()) // 出力: 30歳
	
	// 型アサーションを使って、具体的な型のインスタンスにアクセスできる
	if man, ok := z.(*Man); ok {
		man.ChangeName(man.Name + "dazoooooo")
		fmt.Println(man)
		man.IncrementAge()
		fmt.Println(man)
	} else {
		fmt.Print("man has no Name!!!")
	}
	
	z.IncrementAge()   // インターフェースから直接IncrementAge()は呼び出せずパニックになる(インターフェースには定義されていないため)
	
}

ここで型Manは、インターフェースに定義されていないIncrementAgeメソッドを持っています。

インターフェースに定義されていないメソッドや、型Manの値に直接アクセスしたい場合、上記のように型アサーションを使ってアクセスできます。カンマOKイディオムを使うことで、万が一zの型が違った場合にもパニックを発生させずに処理を続行できます(okfalseが入る)。


インターフェースの使い方にも慣れが必要ですね。
ここまでお読みいただきありがとうございました。


参考


この記事が気に入ったらサポートをしてみませんか?