🦍

《Go入門》『構造体・メソッド』について理解すっぞぉ🦍

2024/09/17に公開

初めに

皆さん、おつかれさマッチョです🦍

私は現在、新プロジェクト開発のためにGoのキャッチアップを行っている朝活ゴリラです。

今回、Goのキャッチアップを進めていく中で《構造体・メソッド》部分について、イマイチ掴み切れてないと実感したため、備忘録も兼ねてこのような記事を作成しました。

本記事の対象読者

  • Go初学者の方
  • 《構造体・メソッド》部分で頭こんがらがっている方

前提条件

当記事ですが、Goの基本構文(変数・定数・関数の定義など)は理解している前提としての記事となります。
予めご了承下さい🙇‍♂️

構造体って何ぞや?

Goの構造体(struct)は、データのまとまりを表すためのものです。

Rubyで言うところの『クラス』ですね。

構造体を使用することで、「フィールド」と呼ばれる複数の異なるデータを、まとめて一つのカスタムデータ型として扱うことができるようになると。

....と、文章だけの説明だと「?????」だと思いますので、実際にコードを使って説明していきます笑

基本的な構造体の定義

まず、Goで構造体を定義する方法としては下記の通りとなります。

type User struct {
  Name string
  Age int
  City string
}

このコードでは、まずtype User struct部分でUserという名前の構造体を定義しています。
くどいようですが、Rubyで言うと『Userクラス』ですね。

そして、このUser構造体に、NameAgeCityという3つのフィールドを定義しています。

この時に注意点として、フィールドを定義する際は必ずデータ型(stringint)を明示しておきましょう!
Goは型付け言語になっているので、「このフィールドにはstring型のデータが入りますよ〜」と明示してやらないと、コンパイル時にエラーが発生してしまいます....!

構造体を使用したコード例

では、先ほどのUser構造体を使って「ユーザーを2人登録、一人目の情報をログに表示し、二人目の情報を一部変更する」というロジックを作成してみたいと思います。

package main

import "fmt"

// 構造体の定義
type User struct {
  Name string
  Age int
  City string
}

func main() {
  // 構造体の変数(インスタンス)を作成
  user1 := User{
    Name: "Gorilla",
    Age: 25,
    City: "Tokushima"
  }
  user2 := User{
    Name: "Rei",
    Age: 1,
    City: "Tokushima"
  }

  // 構造体の各フィールドにアクセスし、ログに表示
  fmt.Printf("Name: %s, Age: %d, City: %s\n", user1.name, user1.Age, user1.City)

  // User構造体のインスタンスのCityフィールドを変更
  user1.City = "Tokyo"
  fmt.Println("Updated City:", user1.City)
}

まず、先ほどのコードと同じようにUser構造体を定義します。

そうしたら次に、User構造体のインスタンス(user1user2)をそれぞれ作成しています。
ちなみに構造体のインスタンスを作成する際は、構造体名{}という構文を使用し、フィールドをキーとして値を指定します。

構造体のフィールドにはドット(.)でアクセスすることが出来、演算子=を使用することで値を変更することが可能です。

構造体のメソッド

実は、Goでは....
構造体に関連するメソッドを定義することも出来ます!👏

例えば、先程のコードでは『user1の情報』のみをログに表示するようにしていましたが、これにuser2の情報も表示させるようにしたい場合、メソッドを使わなければこのように書くことになるかと思います。

package main

import "fmt"

// 構造体の定義
type User struct {
  Name string
  Age int
  City string
}

func main() {
  // 構造体の変数(インスタンス)を作成
  user1 := User{
    Name: "Gorilla",
    Age: 25,
    City: "Tokushima"
  }
  user2 := User{
    Name: "Rei",
    Age: 1,
    City: "Tokushima"
  }

  // 構造体の各フィールドにアクセスし、ログに表示
  fmt.Printf("Name: %s, Age: %d, City: %s\n", user1.name, user1.Age, user1.City)
  fmt.Printf("Name: %s, Age: %d, City: %s\n", user2.name, user2.Age, user2.City)
}

ログ表示の部分で同じような内容のコードが連続しており、これではあまり美しいコードとは言えませんねぇ...(俗にいう冗長的なコード...)

なので、このログ表示ロジックをメソッドを用いてまとめる事にしましょう!
下記がリファクタリングした内容です。

package main

import "fmt"

// 構造体の定義
type User struct {
  Name string
  Age int
  City string
}

// 構造体のメソッド
func (u User) Greet() string {
  return fmt.Printf("Name: %s, Age: %d, City: %s\n", u.name, u.Age, u.City)
}

func main() {
  user1 := User{
    Name: "Gorilla",
    Age: 25,
    City: "Tokushima"
  }
  user2 := User{
    Name: "Rei",
    Age: 1,
    City: "Tokushima"
  }

  // メソッド呼び出し
  user1.Greet()
  user2.Greet()
}

まずメソッド定義ですが、func (u User) Greet() string {...}部分で定義しています。
これは、User構造体に対してGreetというメソッドを定義している事になります。

ちなみに、uですがこれは『レシーバ』と言ってthisに相当する内容となっています。
つまり、u Userとすることで「User型のインスタンスを参照する」という事になります。

メソッドの呼び出しですが、これは構造体のレシーバ(インスタンス).メソッド名()で呼び出すことが出来ます。

レシーバって何じゃい....

さあ、ここまで読み進めていく内にこう思った方も多いのではないでしょうか?

「レシーバってつまり何じゃい....」

ということで、レシーバについて少々深掘りしていきたいと思います。


レシーバとは、ざっくり説明するなら『メソッドがどの構造体のインスタンス(オブジェクト)に関連しているかを指定するためのもの』です。

Goでは、構造体に関連するメソッドを定義する際に、そのメソッドがどの構造体のインスタンスに対して呼び出されるのかを指定するために『レシーバ』を使用します。
レシーバーは、メソッド定義の際にそのメソッドが所属する『構造体のインスタンス』を受け取る役割を担っているんです...!

レシーバの書式

レシーバの書式ですが、先程のfunc (u User) Greet() stringのように、funcキーワードの後にレシーバーが続きます。

  • レシーバーの定義:(u User)
    • uはレシーバ変数。メソッド内でこの変数を通じて、User構造体のフィールドやメソッドにアクセスすることが出来る。
    • UserGreetメソッドが関連付けられる構造体の方である。

何故レシーバが必要なんだぁ??

ほい、ではそんなレシーバですが何故必要なのでしょうか?
ここまで読んでくださった方々なら察している事かと思いますが、念の為に説明させて下さい🙇‍♂️

  1. インスタンスの参照
  • レシーバを使用することで、そのメソッドがどの構造体のインスタンスに対して動作するのかを明示的に指定することが出来ます。
  • 例えば、uを使ってUser構造体のインスタンス(user2)のフィールド(Name, Age, City)にアクセスするといった感じです。
  1. メソッドの関連付け
  • Goでは構造体とメソッドを結びつける際に、レシーバを使ってそのメソッドが特定の構造体のインスタンスで呼び出されることを保証しています。
  • つまり、オブジェクト指向的な設計が可能になるという事です...!

Rubyとの違い

筆者は最初Goのメソッドについてまとめていた時、ふとこう思いました。

あれか?Rubyでいうインスタンスメソッドみたいなもんかぁ!

ですが、厳密にはRubyと少し違うので、ここでRubyでのコードも含めて説明しようと思います。
例えば、下記のようなロジックがあるとしましょう。

class User
  attr_accessor :name

  def user
    self.name = "Gorilla"
  end
end

def new
  @user = User.new
  @user.user
  puts @user.name # "Gorilla"
end

...お粗末なコードですみません🥺(とりあえずの説明のためのコードなので気にしないで下さい)
上記にもあるように、Rubyではクラス内にインスタンスメソッドを定義し、そのメソッド内でselfを使用することで、そのインスタンス自身(@user)を参照し、そのフィールド(name)にアクセスすることが出来ます。


一方Goでは、メソッドがどの構造体のインスタンスに関連しているのかを明示する必要があり、その際に使用するのが『レシーバ』です。

Goのメソッド定義では、そのメソッドがどの構造体のインスタンスを操作するのかを指定するために、レシーバを使って「このメソッドは〇〇構造体のインスタンスと関連していますよ〜」と明示的に示す必要があるという事です!

ちなみに、先程のRubyのロジックをGoで表現すると下記の通りとなります。

package main

import "fmt"

// User構造体の定義
type User struct {
  Name string
}

// User構造体に関するメソッドの定義
func (u *User) setName() {
  u.Name = "Gorilla"  // ポインタレシーバを使い、インスタンスのフィールドの値を変更
}

func main() {
  user := User(
    Name: "Muscle"
  }

  fmt.Println(user.Name) // => "Muscle"

  user.setName()
  fmt.Println(user.Name) // => "Gorilla"
}

ポインタレシーバについては、ここでお話しすると長くなってしまうので、また別で記事にしたいと思います😅

最後に

今回はGoキャッチアップ中に少々「???」となってしまった内容を記事とさせていただきました🦍

新しい言語のキャッチアップは大変な部分もありますが、現在実務で使用しているRubyと比較出来る箇所もちらほらあるので、「Goではこう書くんかぁ〜ほえぇ〜」と楽しみつつキャッチアップ行っております笑

今後もGoのキャッチアップ中に、疑問に思ったことや躓いた内容に関しては記事に落とし込んでいこうと思います!
もし記事内で間違えている箇所等ありましたら、ご指摘いただけますと幸いです🙇‍♂️

HERO Tech Blog

Discussion