はじめに

前回の記事の続きです。
前回の記事はこちら

オブジェクト指向

前回は構造体を勉強したので、活用するために構造体をどのように使うかという話のようです。

method

ここでは、面積 についての話題です。
三角形~n角形やその他の図形での面積を求めるために、図形の構造体に対して method を定義することで
それぞれ面積を求めるものを用意するらしいです。

引用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main
import "fmt"

type Rectangle struct {
    width, height float64
}

func area(r Rectangle) float64 {
    return r.width*r.height
}

func main() {
    r1 := Rectangle{12, 2}
    r2 := Rectangle{9, 4}
    fmt.Println("Area of r1 is: ", area(r1))
    fmt.Println("Area of r2 is: ", area(r2))
}

上記のようなコードが合った際に、
area という名前の関数に area_XXX みたいにn角形用にメソッドを増やすのはナンセンスですね、という話です。
他の言語で言うところのグローバル関数的な感じで定義するのではなく、
method という構造体に結びつく関数を定義することでエレガントになります。

このような理由からmethodの概念が生まれました。
methodはある型に属しています。この文法と関数の宣言の文法はほとんど同じです。
ただ、funcの後にreceiver(methodがくっついているということです)を追加します。

receiver というらしいです。下記のコードを見てみます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main
import (
    "fmt"
    "math"
)

type Rectangle struct {
    width, height float64
}

type Circle struct {
    radius float64
}

func (r Rectangle) area() float64 {
    return r.width*r.height
}

func (c Circle) area() float64 {
    return c.radius * c.radius * math.Pi
}


func main() {
    r1 := Rectangle{12, 2}
    r2 := Rectangle{9, 4}
    c1 := Circle{10}
    c2 := Circle{25}

    fmt.Println("Area of r1 is: ", r1.area())
    fmt.Println("Area of r2 is: ", r2.area())
    fmt.Println("Area of c1 is: ", c1.area())
    fmt.Println("Area of c2 is: ", c2.area())
}

func (r Rectangle) ~ の部分ですね。
オーバーロードに似ていますが、名前は同じだし引数も同じですね。
receiver というのが他の言語でみたことがないので、オーバーロードに近いけど違う感じでしょうか。

reciever の文法です。

1
func (r ReceiverType) funcName(parameters) (results)

methodはまったく同じ名前でもレシーバが異なればmethodも異なります。 methodはレシーバのフィールドにアクセスすることができます。 methodの呼び出しは.を通じて行います。structがフィールドにアクセスするのと同じです。

これだけ見ると、構造体にメンバ関数ができたような感じですね。
オーバーロードではなくて、メンバ関数定義している方が近そう。

上の例では method area() はそれぞれRectangleとCircleに属します。
この時これらの Receiver は Rectangle と Circleになります。
またはこのarea()メソッドはRectangle/Circleを主語とします。

理解はあってるかな。

methodはstruct型以外にも定義できるよって話

読みすすめると、

methodはstructの上でしか使用されないのでしょうか?当然違います。
これはカスタム定義型、ビルトイン型、structなどあらゆる型でも定義することができます。

とのこと。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
type ages int

type money float32

type months map[string]int

m := months {
    "January":31,
    "February":28,
    ...
    "December":31,
}

見た限りだと、 C言語の typedef と同じに見えます。
型のエイリアスを定義しているようです。

次の例
Box BoxList をエイリアスとして定義して、いくつかのmethod も定義しています。
これも、最初の例と同じような感じですね。

1
2
3
4
5
6
7
8
boxes := BoxList {
        Box{4, 4, 4, RED},
        Box{10, 10, 1, YELLOW},
        Box{1, 1, 20, BLACK},
        Box{10, 10, 1, BLUE},
        Box{10, 30, 1, WHITE},
        Box{20, 20, 20, YELLOW},
    }

ここは本来

1
2
3
boxes := []Box{
        // 中身 
        }

として書くべきところをエイリアスで書いてますね。

また、

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
type Color byte

type Box struct {
    width, height, depth float64
    color Color
}

func (c Color) String() string {
    strings := []string {"WHITE", "BLACK", "BLUE", "RED", "YELLOW"}
    return strings[c]
}

として宣言してあって、

1
fmt.Println("The color of the last one is",boxes[len(boxes)-1].color.String())

こんな感じで、 Boxcolorフィールドの methodString を呼んでたりします。
ちょっと複雑になってきましたが、読み解いていけば、やったことの組み合わせですね。

ポインタとしてのreceiver

値渡し参照渡し の延長ですね。
ポインタを渡さないで値で渡すとコピーになっちゃうので、
呼び出し元の方で引数に入れた変数が更新されないよという話。

receiver のポインタはコンパイラで自動的に解釈してくれる。

PointItBlackの中でSetColorをコールした時、 ひょっとして(&bl[i]).SetColor(BLACK)と書かなければならないんじゃないかと。 SetColorのreceiverは*Boxであり、Boxではありませんから。

ここは確かに。
ポインタであればアドレスを取得しないとな~と思ったのですが、
bl[i].SetColor(BLACK) で良いらしい。
構文が複雑にならないのは良いですね。

method継承

2つのコード例があります。 順に実行してみました。

1つ目は、Student , Employee 内にある Humen の匿名フィールドに関して、
HumanmethodSayHi をコールしています。
paiza-io-golang-01

見た感じ、SayHi の関数の内容で出力されています。

2つ目は、それに対して、Employeemethod として SayHi を定義しています。
そして、同じようにコールしています。

paiza-io-golang-02

このとき、2個目にコールされた sam.SayHi() の出力が、 EmployeemethodSayHi の内容となっております。
他の言語だとオーバライドしていると言えるかと思います。

クラスなしでやっていくのにはこんな感じなんだなというのがなんとなくしれました。

おわりに

次回は、interface をやっていきます。
A tour of Go とか 実践 Go言語 とか やっておきたいのですが、まだ手が回っていないです。
プログラミング言語 Goという書物がバイブル的存在らしいので、そちらも読んでみたいです。