A Tour of Goのざっくりまとめ⑥ <Methods and Interfaces編>
前回までの続きです。
mattsun-plapla.hatenablog.com
mattsun-plapla.hatenablog.com
mattsun-plapla.hatenablog.com
mattsun-plapla.hatenablog.com
mattsun-plapla.hatenablog.com
Methods
Goにクラスは存在しないため、代わりにメソッドが定義できる。
メソッドを定義する際、レシーバを引数に取る。
下記の例では、add関数は引数にVertex型のレシーバを受け取る。
このvからadd関数を呼び出せるため、オブジェクト指向的に見える。
import "fmt" type Vertex struct { X, Y int } // 通常の記法 func add(v Vertex) int{ return v.X + v.Y } // メソッド func (v Vertex) add() int { return v.X + v.Y } func main() { v := Vertex{30, 40} fmt.Println(v.add()) // 70 }
ポインタレシーバ
レシーバがポインタの場合、それはポインタレシーバである。
レシーバ自身のもつ値を変更する際はこちらを使うため、通常の変数レシーバよりも使われることが多いらしい。
下記の例では、Scale関数はVertexのポインタを引数にとるポインタレシーバである。
もしアスタリスクを無くして、変数レシーバにした場合、元のレシーバが指す変数の値自体は変わらない。
type Vertex struct { X, Y int } /* ポインタを引数にとるポインタレシーバ */ func (v *Vertex) Scale(f int) { v.X = v.X * f v.Y = v.Y * f } func main() { v := Vertex{30, 40} v.Scale(2) fmt.Println(v) //{60 80} }
ここで、通常ポインタを受け取るためには、アドレスを渡さないといけなかった。
上の例だと、vはただの変数であってアドレスではない。
なのに自動的にポインタレシーバが呼び出され、ポインタのもつ実体も変わった。
理由として、ポインタレシーバを持つ場合、Goは利便性を考慮して、自動的にv.Scale(2)を(&v).Scale(2)と解釈してくれるから。
したがって、わざわざ下記のように書かなくてもいいというわけである。
p := &Vertex{50, 50} p.Scale(2) fmt.Println(*p) //{100 100}
ポインタレシーバを使う理由
- レシーバが指す実体の値を変更するため
- メソッドの呼び出しごとに変数を複製することを避けるため
2つ目は、大きな構造体などを扱う場合にメリットがあるらしい。
Interfaces
メソッドの集まりをinterface型の変数へ持たせることができる。
...とはいえよくわかりません。原文の解説を見てみます。
An interface is a collection of method signatures that a Type can implement (using methods). Hence interface defines (not declares) the behavior of the object (of the type Type).
...やっぱりよくわかりません。
インターフェース上ではメソッドシグネチャを定義します。(※メソッドシグネチャについては下記リンクが参考になりました。)
インターフェースは、メソッド名と引数、返り値から構成されるメソッドシグネチャを提供するものです。
import ( "fmt" "math" ) type geometry interface { area() float64 perim() float64 } type rect struct { height, width float64 } type circle struct { radius float64 } func (r rect) area() float64 { return r.width * r.height } func (r rect) perim() float64 { return 2*r.width + 2*r.height } func (c circle) area() float64 { return math.Pi * c.radius * c.radius } func (c circle) perim() float64 { return 2 * math.Pi * c.radius } func measure(g geometry) { fmt.Println(g) fmt.Println(g.area()) fmt.Println(g.perim()) } func main() { r := rect{4, 5} c := circle{8} measure(r) measure(c) }
まず、areaとperimのメソッドをもつインターフェースgeometryを定義する。
続いて、構造体rectとcircleを定義する。
これらを引数とするarea, perimメソッドはインターフェース上に定義されている。
変数がインターフェース型をもつ場合、インターフェース上の各メソッドを呼び出せる。
インターフェース上で宣言したメソッドはすべて実装しないとエラーになる。
空のインターフェース
空のインターフェースは、任意の型の値を保持できます。 (全ての型は、少なくともゼロ個のメソッドを実装しています。)
空のインターフェースは、未知の型の値を扱うコードで使用されます。 例えば、 fmt.Print は interface{} 型の任意の数の引数を受け取ります。
https://go-tour-jp.appspot.com/methods/14
インターフェースがメソッドを持っていない場合、空のインターフェースと呼ばれる。
これを使えば、異なる型のデータを呼び出してプリントすることもできる。
type MyString string type Rect struct { width, height float64 } func explain(i interface{}) { fmt.Printf("value given to explain this function is of type %T with %v\n", i, i) } func main() { ms := MyString("My massage is here!") rect := Rect{30, 40} explain(ms) // value given to explain this function is of type main.MyString with My massage is here! explain(rect) // value given to explain this function is of type main.Rect with {30 40} }
explain関数は空のインターフェースを引数に取るため、string型のMyStringやstruct型のRectも受け取ることができる。
Type Assertion
func main() { var i interface{} = "Hello world" s := i.(string) fmt.Printf("type is %T value is %v\n", s, s) // type is string value is Hello world s2, ok := i.(string) fmt.Println(s2, ok) // Hello world true s3, ok := i.(int) fmt.Println(s3, ok) // 0 false s4 := i.(float64) fmt.Println(s4) // panic: interface conversion: interface {} is string, not float64 }
型アサーションにより、インターフェースのある値を取り出すことができる。
t := i.(T)
インターフェースiにある具体的な型Tを保持する。
そしてTの値をtに代入している。
iがTを保持していれば、tはその値が代入されるが、保持していない場合、返り値にokを加えてテストをしないとpanic errorとなる。
Type switches
複数の型アサーションができる構文。
import "fmt" func do(i interface{}) { switch v := i.(type) { case string: fmt.Printf("Expected String type is %T / value is %v\n", v, v) case int: fmt.Printf("Expected Int type is %T / value is %v\n", v, v) default: fmt.Printf("type is %T / value is %v\n", v, v) } } func main() { do(1) // Expected Int type is int / value is 1 do("OK") // Expected String type is string / value is OK do(true) // type is bool / value is true }