Skip to content

Goのメソッドとインターフェース

Goはクラスを持たないため、メソッドは構造体や他のユーザー定義型に紐づけられます。また、インターフェースは特定の振る舞いを定義する契約のようなもので、型がその振る舞いを暗黙的に実装することで満たされます。

メソッドは、レシーバー引数を持つ特殊な関数です。このレシーバー引数により、メソッドは特定の型に関連付けられます。メソッドは、その型が持つデータを操作したり、その型の振る舞いを定義したりするために使われます。

// Userという構造体を定義
type User struct {
Name string
Age int
}
// User構造体にSayHelloメソッドを定義
// (u User) がレシーバー引数
func (u User) SayHello() {
fmt.Printf("Hello, my name is %s and I'm %d years old.\n", u.Name, u.Age)
}

このSayHelloメソッドは、User型のインスタンスに対してのみ呼び出すことができます。

// メソッドの呼び出し
user := User{Name: "Alice", Age: 30}
user.SayHello()

インターフェースは、メソッドのシグネチャ(名前、引数、戻り値)の集合を定義する型です。Goのインターフェースは暗黙的に実装される点が特徴です。つまり、ある型がインターフェースに定義されたすべてのメソッドを実装していれば、その型は自動的にそのインターフェースを満たすと見なされます。implementsのような明示的なキーワードは不要です。

// Greeterインターフェースを定義
type Greeter interface {
SayHello()
}
// User構造体は既にSayHello()メソッドを実装しているため、Greeterインターフェースを満たす
func Greet(g Greeter) {
g.SayHello()
}
func main() {
user := User{Name: "Alice", Age: 30}
// Greet関数はGreeterインターフェースを受け取る
// UserはGreeterを暗黙的に実装しているため、引数として渡せる
Greet(user)
}

この「暗黙的な実装」の仕組みは、Goが疎結合なコードを構築する上で非常に重要です。関数やメソッドは具体的な型ではなくインターフェースに依存することで、異なる実装を持つ型を柔軟に扱うことができるようになります。

これらの概念を理解することで、Goの型システムが単なるデータ構造の定義にとどまらず、振る舞いを基にした柔軟な設計を可能にしていることがわかります。

ポインタレシーバーと値レシーバー

Section titled “ポインタレシーバーと値レシーバー”

Goのメソッドには、ポインタレシーバーと値レシーバーという重要な違いがあります。これは、メソッド内でレシーバーの値を変更するかどうかを決定する上で非常に重要です。

ポインタレシーバーを使用すると、メソッド内でレシーバーの元の値を変更できます。これは、メソッドがレシーバーのメモリ上のアドレスを参照するためです。

type User struct {
Name string
Age int
}
// Ageを1増やすメソッド
func (u *User) IncrementAge() {
u.Age++ // レシーバーの元のAgeを直接変更する
}
func main() {
user := User{Name: "Alice", Age: 30}
user.IncrementAge()
fmt.Println(user.Age) // 出力: 31
}

この場合、IncrementAgeメソッドはuser変数のAgeを直接変更します。

値レシーバーを使用すると、メソッドはレシーバーの値のコピーを受け取ります。このため、メソッド内でレシーバーの値を変更しても、元の変数には影響がありません。

type User struct {
Name string
Age int
}
// Ageを1増やすメソッド
func (u User) IncrementAge() {
u.Age++ // レシーバーのコピーを変更する
}
func main() {
user := User{Name: "Alice", Age: 30}
user.IncrementAge()
fmt.Println(user.Age) // 出力: 30
}

この場合、IncrementAgeはuserのコピーを操作するため、元のuserのAgeは変わりません。

一般的に、以下のガイドラインに従うと良いでしょう。

  • ポインタレシーバー:

    • メソッド内でレシーバーのフィールドを変更する必要がある場合。
    • 構造体が大きい場合(値のコピーを避けるため、パフォーマンスが向上します)。
    • スライスやマップ、チャネルなど、参照型のフィールドを持つ構造体の場合。
  • 値レシーバー:

    • メソッドがレシーバーの値を変更しない場合。
    • メソッド内でレシーバーのコピーを操作したい場合(元の値を保護するため)。