RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

Go言語 入門【メソッドの作り方】

こんにちは、sakekobaと申します。
今回は人気の高いGo言語の「メソッド」について記事を書きたいと思っております。

また、Go言語については、当ブログで先輩方が素敵な記事を複数記載されております。
こちらも併せてご覧頂けましたら幸いです。

Go言語 入門【環境構築とコーディング】 - RAKUS Developers Blog | ラクス エンジニアブログ
Go言語の平行処理をやってみよう!【goroutine】 - RAKUS Developers Blog | ラクス エンジニアブログ

目次

メソッドとは?

Go言語だけでなく、メソッドを持つ言語は多いと思います。
「メソッド」と聞くと、まずオブジェクト指向プログラミングを思い浮かべる方も多いのではないでしょうか。

メソッドとは、主にオブジェクト指向言語で使われる言葉で、オブジェクトが持つ処理や手続きの方法を指す言葉です。
例えば、 「車」というオブジェクトであれば「走る」「ドアを開ける」「ドアを閉じる」等の処理(メソッド)が紐づいているイメージです。
Java等では「クラス」という形でオブジェクトを定義して、そのクラスにメソッドも定義されていて、そこからインスタンスを生成して使う。。のような手順を踏みます。

ところが、Go言語はこの上記のような「クラス」は使用されません。
では、Go言語のメソッドはどこに定義されるのでしょうか。

Go言語のメソッドは何に紐づくの?

Go言語の場合は「型」に直接紐づきます。

「型」といっても、Go言語の型はintやstringといった基本の型だけでなく、type という型を定義する予約語を用いて基本の型を拡張し、オリジナルの型を作ることが出来ます。
この type を使って拡張した型に、メソッドを追加することが出来ます。

たとえば、基本の型であるstring型を拡張(string型に様々な機能を追加出来るように)するMyStringという型を作る場合、Go言語では下記のように書いて定義することが出来ます。

type MyString string

この新しく定義したMyStringに、下記のようにメソッドを追加することが出来ます。(例は敬称の「さん」を返すメソッド)

func (s MyString) Keisyo() string {
    return "さん"
}

このように型に紐づけるのがGo言語におけるメソッドです。

メソッドの書き方、使い方

Go言語のメソッドは

func (レシーバ 型) メソッド名(引数) 戻り値 {
    処理
}

という形で書きます。
(レシーバ 型)の部分は、メソッドが紐づく型(typeで指定した拡張型)と、その型からメソッドを呼び出す際に指定した変数が持つ値を受け取る変数名です。
先ほどの例では、 レシーバとして変数sを用意し、レシーバの型がtypeを使って定義したMyString、メソッド名がKeisyo()で、戻り値がstringです。

func (s MyString) Keisyo() string {
    return "さん"
}

呼び出す際は下記のように、レシーバの型で変数を作成し、その変数にドットをつけてメソッド名を記載します。

func main() {
    namae := MyString("たなか")
    fmt.Print(namae, namae.Keisyo())
}

こうして呼び出したメソッドは、呼び出し元(ドットの前の型)と一致するレシーバの型を持つメソッドが呼び出されます。

型の定義、メソッドの定義から呼び出しまでを続けて書くと下記のような形になります。

package main

import (
    "fmt"
)

type MyString string

func (s MyString) Keisyo() string {
    return "さん"
}

func main() {
    namae := MyString("たなか")
    fmt.Print(namae, namae.Keisyo())
}

⇒出力結果

たなかさん

になります。

同一のメソッド名について

メソッドは、「呼び出しに指定した型と一致するレシーバを持つメソッド」が呼び出されます。
つまり、レシーバの型さえ違えば、同一のメソッド名を作成することが可能です。

例えば、先ほどの例ではKeisyo()を呼び出すと「さん」が返ってきていました。
ただ、呼び出し元の型によって敬称を使い分けたい場合、下記のようにtypeで型を複数定義し、typeで拡張した型ごとにメソッドを持たせることで、同じメソッド名で違う振る舞いをさせることが出来ます。

package main

import (
    "fmt"
)

type MyString1 string
type MyString2 string

func (s MyString1) Keisyo() string {
    return "さん"
}

func (s MyString2) Keisyo() string {
    return "くん"
}

func main() {
    namae1 := MyString1("たなか")
    namae2 := MyString2("さとう")
    fmt.Println(namae1, namae1.Keisyo())
    fmt.Println(namae2, namae2.Keisyo())
}

⇒出力結果

たなか さん
さとう くん

になります。

インターフェースについて

上記のように、同一のメソッド名を複数の型に持たせることが出来ます。
ただ、呼び出し元と同一のメソッド名しか呼び出せません。
Go言語は静的型付け言語です。変数がカッチリ定義されているので、1つの型として定義された変数には、たとえstringを基にしただけの似たような型であっても、別の型の変数を入れることは出来ません。

func main() {
    onamae := MyString1("たなか") // ここでonamaeはMyString1の型として定義されてしまいます
    fmt.Println(onamae, onamae.Keisyo())

    onamae = MyString2("さとう") // onamaeはMyString1の型として使われたためエラーになります
    fmt.Println(onamae, onamae.Keisyo())
}

つまり、Go言語で複数の型を使い分けようとすると、このままではif文等で分岐して型ごとの処理を書く必要があり、ソースコードとしてはとても煩雑になります。

どちらもKeisyo()というメソッド名を持っているので、まとめられたら楽そうです。

そのような時に使えるのが、まとめてメソッドを定義、実行できるインターフェースです。

Go言語のインターフェースの定義はシンプルで、下記のように記載すると、stringの戻り値を持つKeisyo()メソッドを持つ拡張型は全て、ONAMAEインターフェースの仲間入りをすることが出来ます。

type ONAMAE interface {
    Keisyo()  string
}

こうしておくと、先ほどのMyString1型も、MyString2型も、Go言語ではONAMAEインターフェースで使うことが出来ます。
そのため下記のように、ONAMAEインターフェースに型を入れるだけで同じ呼び出し方法fmt.Println(onamae,onamae.Keisyo())で違う振る舞いをさせることが出来ます。

package main

import (
    "fmt"
)

type ONAMAE interface {
    Keisyo()  string
}

type MyString1 string
type MyString2 string

func (s MyString1) Keisyo() string {
    return "さん"
}

func (s MyString2) Keisyo() string {
    return "くん"
}

func main() {
    var onamae ONAMAE
    onamae = MyString1("たなか")
    fmt.Println(onamae, onamae.Keisyo())

    onamae = MyString2("さとう")
    fmt.Println(onamae, onamae.Keisyo())
}

⇒出力結果

たなか さん
さとう くん

になります。

関数化しておくなどで使いまわす場合、おなじstringを戻り値としたKeisyo()さえ持っていれば、例えば「様」や「どの」を追加したくなった時など、色々な敬称の処理を追加しやすくなります。
また、インターフェースは下記のように、中に入っているタイプによって処理を変える事も出来る為、受け取った内容によって振る舞いを変えたいけど、処理をまとめたいという場合にはとても便利だと感じます。

switch onamae.(type) {
case MyString1:
    fmt.Println("こんにちは")
case MyString2:
    fmt.Println("やっほー")
}

今までのコードと繋げると、下記のようにonamaeに入った型によって、挨拶が変わります

package main

import (
    "fmt"
)

type ONAMAE interface {
    Keisyo()  string
}

type MyString1 string
type MyString2 string

func (s MyString1) Keisyo() string {
    return "さん"
}

func (s MyString2) Keisyo() string {
    return "くん"
}

func main() {
    var onamae ONAMAE
    onamae = MyString1("たなか")
    fmt.Println(onamae, onamae.Keisyo())

    switch onamae.(type) {
        case MyString1:
            fmt.Println("こんにちは")
        case MyString2:
            fmt.Println("やっほー")
    }

    onamae = MyString2("さとう")
    fmt.Println(onamae, onamae.Keisyo())

    switch onamae.(type) {
        case MyString1:
            fmt.Println("こんにちは")
        case MyString2:
            fmt.Println("やっほー")
    }
}

⇒出力結果

たなか さん
こんにちは
さとう くん
やっほー

になります。

最後に

今回はGo言語のメソッドについて、記事を書かせて頂きました。
ただ、私は普段Go言語を使って業務を行っているわけではありません。
むしろインフラ管理等を行っており、私はあまりプログラムに馴染みが無い状態です。

今、ラクスではSRE課の方が先導して、Go言語の勉強会を開催してくださっています。
私のような初心者でも分かるよう、実例を交えてイメージしやすく教えてもらうことが出来、この記事を書こうと思えるまでに理解を深めることが出来ました。
直近の実務のことだけでなく、将来を見据えてGo言語のような需要がありそうな技術の勉強会を開催頂き、そこへの参加機会があるのは、ラクスの良いところだと思います!


エンジニア中途採用サイト
ラクスでは、エンジニア・デザイナーの中途採用を積極的に行っております!
ご興味ありましたら是非ご確認をお願いします。
20210916153018
https://career-recruit.rakus.co.jp/career_engineer/

カジュアル面談お申込みフォーム
どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。
以下フォームよりお申込みください。
rakus.hubspotpagebuilder.com

ラクスDevelopers登録フォーム
20220701175429
https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/

イベント情報
会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください!

◆TECH PLAY
techplay.jp

◆connpass
rakus.connpass.com

Copyright © RAKUS Co., Ltd. All rights reserved.