【Golang】メソッドについて今一度おさらいしてみる
どもども、引越しまでおおよそ1ヶ月ですが進捗ゼロな僕です。
現在勤めている会社では提供しているWebサービスのGo移行計画があり、その波に乗せてもらう形で僕もGolang勉強会を通した実装力向上に努めています。講師をしてくださっている先輩社員には頭が上がりません。
独学の頃よりも明らかにGoへの理解が深まってきている自信が出てきたので、独学の頃は本当に理解できなかったメソッドについて今一度記事にまとめ理解を深めていきたいと思います。
構造体について
まずは構造体(struct)について確認していきます。メソッドを扱う上で理解できていることが前提となるのがこの構造体です。
構造体を使用することで、フィールドと型の組み合わせた複数を要素を定義することができます。定義する上で大切なのは“この構造体が定義しているモノはなにか"を意識することです。
例えば下記の例を見てください。構造体Userはユーザーについて定義されていることが予想できますね。
type User struct {
Name string
Age int
}
構造体はただの定義なのでこのままでは使用できません。それぞれのフィールドに値を入れて初期化してあげましょう。初期化した構造体は変数に入れることで使用できる状態になります。あとは“変数.フィールド名"で対応する値を取得できるようになります。
func main() {
//値を入れて初期化する
tom := User{
Name: "Tom",
Age: 20,
}
fmt.Println(tom.Name, tom.Age)
// Tom 20
}
ここまでの全体像としては以下のとおりです。
package main
import "fmt"
type User struct {
Name string
Age int
}
func main() {
tom := User{
Name: "Tom",
Age: 20,
}
fmt.Println(tom.Name, tom.Age)
// Tom 20
}
今回はただ値を入れて出力するだけでしたが、例えば構造体の値をDBに保存することでユーザ作成を行うことができますね。
構造体はモノを定義し、そのモノを関数やメソッドを使用して処理していくというイメージをもっておいてください。
メソッドについて
次にメソッドについて確認していきましょう。メソッドを学ぶ際に200%ぶつかる疑問として"関数と何が違うの?"という点があるとおもいます。(僕調べ)
なので今回はユーザー情報(名前と年齢)を表示する関数とメソッドを定義することでそれぞれの違いを理解していくことにしましょう。
まずは関数で実装してみようと思います。先程の構造体のセクションではmain関数に直接情報を表示する処理を書いています。
fmt.Println(tom.Name, tom.Age)
これをUserShow関数として切り出すと下記のようになります。
type User struct {
Name string
Age int
}
// 引数はUserとなる
func UserShow(user User) {
fmt.Println(user.Name, user.Age)
}
func main() {
tom := User{
Name: "Tom",
Age: 20,
}
// 初期化した構造体を引数とする
UserShow(tom)
// Tom 20
}
UserShow関数には初期化した構造体User(が格納されている変数tom)を渡しているので、関数側の引数はUser型となります。実行するとユーザ名と年齢が表示されましたね。
次にメソッドを使用して同じ機能を実装してみましょう。メソッドでは下記のように定義します。
type User struct {
Name string
Age int
}
// メソッドを定義
// 構造体Userを指定している
func (u User) Show() {
// 構造体Userの値を扱うことができる
fmt.Println(u.Name, u.Age)
}
func main() {
tom := User{
Name: "Tom",
Age: 20,
}
// Showメソッドを呼び出す
tom.Show()
}
一見関数と何が違うのかと思うかもしれませんが、メソッド名の前に丸括弧があり今回は構造体を指定しています。この括弧が関数とメソッドを見分けるポイントです。その後にメソッド名を定義し引数を指定する括弧があるといった形になっています。
メソッドの個人的な解釈としては“何かに所属している関数"です。何に所属しているかが最初の丸括弧で定義されています。今回の場合は構造体Userということになります。
イメージとしては構造体Userが保持するShowメソッドを定義したといったところでしょうか。また、メソッドは所属元の値を扱うことができるので今回の場合は構造体Userに格納された値をShowメソッドは参照することができます。
呼び出し方としては"所属元.メソッド名(引数)"といった形になります。
今回は変数tomに構造体Userが初期化され代入されているので"tom.Show()"で呼び出しています。上記を実行すると関数と同じ結果を得ることができるようになると思います。
ここで関数名はUserShowだったのに対し、メソッド名はShowだけになっていることを覚えておいてください。
メソッドを定義する意味
さて、これで関数とメソッドのコード上での違いはなんとなく理解できたと思います。しかし、関数で実装できるものをどうしてメソッドでも定義できる必要があるのでしょうか。
これは実際にアプリケーションを実装する場面を想像すると理解しやすいです。たとえば共同編集できるメモアプリを開発するとしましょう。
ユーザー情報に加えてメモ情報も表示できるようにしないといけませんね。扱うモノが増えました。
モノが増えたので構造体を用意しなければなりません。持つ情報はタイトルと概要としましょう。
type Memo struct {
Title string
Context string
}
上記のような構造体を新しく定義します。構造体User同様使用する場合は初期化が必要です。
manual := Memo{
Title: "Manual",
Summary: "This is Manual",
}
// 呼び出し方
manual.Show()
関数を使用せずメソッドで表示する機能を実装すると以下のようになります。
func (u Memo) Show() {
fmt.Println(u.Title, u.Summary)
}
メソッドの大きな利点として、関数は“何をどうするのか"がセットになっているのに対し、メソッドは“何"を"どうするのか"を切り分けて考えることができる点にあります。
切り分けて考えることで扱うモノが増えた場合にも柔軟に対応することができます。
扱う方法が増えた場合もわかりやすいです。アプリケーションの動作には表示以外にも作成、削除、更新など様々な方法がありますね。
“何を"の部分、いわゆる扱うモノは構造体によって定義されているので、あとはそのモノに所属する形で “どうするか"を定義するのみです。そのため関数とは違いメソッド名はShowといった名前で定義されるというわけです。
// "ユーザー"を"作成する"
func (u User) Create() {
<割愛!>
}
// "ユーザー"を"削除する"
func (u User) Delete() {
<割愛!>
}
// "メモ"を"更新する"
func (u Memo) Updata() {
<割愛!>
}
また、それぞれの構造体とメソッドはモノごとにファイル分けすることでより見通しがよくなります。
package object
import "fmt"
type User struct {
Name string
Age int
}
func (u User) Show() {
fmt.Println(u.Name, u.Age)
}
package object
import "fmt"
type Memo struct {
Title string
Summary string
}
func (u Memo) Show() {
fmt.Println(u.Title, u.Summary)
}
ファイルわけすることでmain.goの中身はさらに簡単になります。(objectパッケージをインポートするためにgo mod initやgo mod tidyを忘れずに!)
package main
import (
"test/example/object"
)
func main() {
//objectパッケージから呼び出す
tom := object.User{
Name: "Tom",
Age: 20,
}
manual := object.Memo{
Title: "Manual",
Summary: "This is Manual",
}
tom.Show()
manual.Show()
}
objectディレクトリに扱うモノとそれぞれがどう処理されるかが定義されていることさえわかっていれば、呼び出す方はとてもやりやすくなります。
このように、開発者はメソッドを使用することでアプリケーション内で扱われているモノが何でどんな処理をするのか。また、それぞれがどこに定義されているのかが関数で定義するよりも圧倒的に分かりやすくなります。
まとめ
本日はGoのメソッドについて再度記事にしてみました。
コード上の解説にとどまらず、その捉え方まで意識して記事にしたつもりでしたがいかがだったでしょうか。
僕自信普段SREエンジニアとして勤務している関係上実装経験が浅く、個人の解釈を多くとりいれているので若干心配ではありますが、Go学習者の一助となれば幸いです。
最後まで読んでいただきありがとうございました!
本日のオススメ