Go言語の構造体に関数を定義したあとに、動的に処理を変更することはreflect
を使わない限りできないです。
ただ、スタブやモックとして関数の挿げ替えを行いたい場合や、実装を動的に変更したいという特質な要件を持つ人もたまにはいるでしょう。
そんなときはC言語っぽい考えで実装をしてしまいましょう。
TL;DR
type Foo struct { stringFunc func(Foo) string } func (f Foo) String() string { if f.stringFunc == nil { return "" } return f.stringFunc(f) }
構造体のフィールドに関数
C言語の復讐
C言語でオブジェクト指向っぽいものが流行っていた時は下記のように書くことがあったかと思います。
#include <stdio.h> #include <stdlib.h> struct foo { char *name; void (*func)(struct foo*); }; void foo_func(struct foo* f) { printf("@%s\n", f->name); } int main(int argc, char* argv[]) { struct foo* f = (struct foo*)malloc(sizeof(struct foo)); f->name = "kaneshin"; f->func = foo_func; f->func(f); return 0; }
関数のポインタ変数を構造体のメンバーに定義しておき、そこに対してApplyするような実装になっています。
Go言語で実装
このC言語の特徴をGo言語でもうまく使ってしまおうということです。(関数オーバーヘッド?なにそれおいしいの?)
package main import "fmt" type User struct { name string stringer func(User) string } func (u User) String() string { return u.stringer(u) }
User
という構造体のフィールドにstringer
という関数を作っています。引数に自身を渡すのはC言語の方を理解している人なら当たり前にわかると思いますが、呼び出し先ではどのような構造体の値からの呼び出しなのかが判断つかないので、呼び出し時にセットします。
これはGo言語でも同じなので気をつけてください。
func printer(f fmt.Stringer) { fmt.Printf("Stringer: %s\n", f.String()) } func main() { u := User{name: "kaneshin"} // @<user.name> u.stringer = func(u User) string { return fmt.Sprintf("@%s:", u.name) } printer(u) // Hello <user.name> u.stringer = func(u User) string { return fmt.Sprintf("Hello %s", u.name) } printer(u) }
Goの方ではfmt
パッケージのStringer
型 (interface
) を満たすようしています。
上記のmain
の中身を見てもらうとわかるかと思いますが、String()
の中で呼ばれているstringer
をここで設定しています。
おわりに
こういう実装をしていくと、定義が別の関数内に入ってしまったりするので、すべてのコードがテスタブルなコードを書くようにするにはまた一工夫必要であったりします。 また、関数が設定されていない場合はBAD ACCESSになるので気をつけてください。
この実装方法に慣れてしまって動的に変更したくなったとしても、用法・用量は守りましょう。