golangではtypeによる型定義とレシーバと呼ばれるメソッド定義ができます。
複数の外部パッケージを同時使用するときなどにレシーバ追加をしたいときがあるんですが、言語規則のためそのままだと追加できません。
基本的なことからおさらいしつつ対策を考えます。
型定義とレシーバ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package main import ( "fmt" ) type MainType struct { Name string } func (m *MainType) Say() string { return "my name is " + m.Name } func main() { m := MainType{"main"} fmt.Println(m.Say()) //-> my name is main } |
型名やフィールド名の最初を大文字にすることでグローバルスコープにできます。
ローカルだけで使ったり、セッターゲッターでやり取りする場合は小文字にするのがよさそうですが、templateのような外部パッケージで使う時に困ることもあるので基本はグローバルでいいと思います。
ところで厳密に見るとこのメソッドはおかしいです。
ポインタに対するアクションを定義しているのでなのでm:=&MainType{"main"}
としないといけない気がしますが、GOのコンパイラがうまいこと処理してくれます。
つまり逆転しても正常動作します。
1 2 3 4 5 6 7 8 9 |
func (m MainType) Say() string { return "my name is " + m.Name } func main() { m := new(MainType) //= &MainType{} m.Name = "main" fmt.Println(m.Say()) //-> my name is main } |
とはいえ実情に合わせて使ったほうが良いです。
セッターなどを実装する場合にはポインタレシーバでないとうまく動かせないことがあります。これは関数の引数と同じように考えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
func (m MainType) Set(name string) { m.Name = name } func (m *MainType) Setp(name string) { m.Name = name } func main() { e := MainType{"main"} e.Set("change") fmt.Println(e.Say()) //-> my name is main e.Setp("change") fmt.Println(e.Say()) //-> my name is change } |
後は呼び出し元がnil
でないことだけ注意します。
ところで構造体の中身に適当な処理をしたいだけなら普通に関数を使うべきです。
1 2 3 |
func Say(m *MainType) string { return "my name is " + m.Name } |
この場合は厳密な型が要求されるので渡す型には注意する。
レシーバは汎用的なインターフェースの実装のために使うものだと思います。
メソッドチェーンが気持ちよすぎてなんでもレシーバにしてしまうことがありますが大体混乱のもとです。
外部パッケージのレシーバ定義
外部パッケージAのインターフェース引数に外部パッケージBの型を渡そうとするときなどにやりたくなります。
具体的にはginのStaticFS()にstatik(http.FileSystem)の型を入れるときなどです。
テストとしてサブパッケージを作って読み込みます。
ローカルパッケージ以外のレシーバ定義はエラーになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
//ext/ext.go package ext type ExtType struct { Name string } //main.go package main import ( "./ext" ) //エラー:cannot define new methods on non-local type ext.ExtType func (e ext.ExtType) Say() string { return "my name is " + e.Name } |
つまりローカルにそのまま定義しなおせばメソッドを追加できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package main import ( "fmt" "./ext" ) //定義しなおし type MainExtType ext.ExtType func (m MainExtType) Say() string { return "my name is " + m.Name } func main() { e := MainExtType{"ext"} fmt.Println(e.Say()) //-> my name is ext } |