はじめに
前回の記事の続きです。
前回の記事はこちら
オブジェクト指向
前回は構造体を勉強したので、活用するために構造体をどのように使うかという話のようです。
method
ここでは、面積
についての話題です。
三角形~n角形やその他の図形での面積を求めるために、図形の構造体に対して method
を定義することで
それぞれ面積を求めるものを用意するらしいです。
引用
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
というらしいです。下記のコードを見てみます。
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
の文法です。
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などあらゆる型でも定義することができます。
とのこと。
type ages int
type money float32
type months map[string]int
m := months {
"January":31,
"February":28,
...
"December":31,
}
見た限りだと、 C言語の typedef
と同じに見えます。
型のエイリアスを定義しているようです。
次の例Box
BoxList
をエイリアスとして定義して、いくつかのmethod
も定義しています。
これも、最初の例と同じような感じですね。
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},
}
ここは本来
boxes := []Box{
// 中身
}
として書くべきところをエイリアスで書いてますね。
また、
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]
}
として宣言してあって、
fmt.Println("The color of the last one is",boxes[len(boxes)-1].color.String())
こんな感じで、 Box
のcolor
フィールドの method
のString
を呼んでたりします。
ちょっと複雑になってきましたが、読み解いていけば、やったことの組み合わせですね。
ポインタとしてのreceiver
値渡し
と参照渡し
の延長ですね。
ポインタを渡さないで値で渡すとコピーになっちゃうので、
呼び出し元の方で引数に入れた変数が更新されないよという話。
receiver
のポインタはコンパイラで自動的に解釈してくれる。
PointItBlackの中でSetColorをコールした時、 ひょっとして(&bl[i]).SetColor(BLACK)と書かなければならないんじゃないかと。 SetColorのreceiverは*Boxであり、Boxではありませんから。
ここは確かに。
ポインタであればアドレスを取得しないとな~と思ったのですが、bl[i].SetColor(BLACK)
で良いらしい。
構文が複雑にならないのは良いですね。
method継承
2つのコード例があります。 順に実行してみました。
1つ目は、Student
, Employee
内にある Humen
の匿名フィールドに関して、Human
の method
の SayHi
をコールしています。
見た感じ、SayHi
の関数の内容で出力されています。
2つ目は、それに対して、Employee
の method
として SayHi
を定義しています。
そして、同じようにコールしています。
このとき、2個目にコールされた sam.SayHi()
の出力が、 Employee
の method
の SayHi
の内容となっております。
他の言語だとオーバライドしていると言えるかと思います。
クラスなしでやっていくのにはこんな感じなんだなというのがなんとなくしれました。
おわりに
次回は、interface
をやっていきます。A tour of Go
とか 実践 Go言語
とか やっておきたいのですが、まだ手が回っていないです。プログラミング言語 Go
という書物がバイブル的存在らしいので、そちらも読んでみたいです。