Goのreflectパッケージを使ってインスタンスを生成する

Goのreflectパッケージを使ってインスタンスを生成する
目次

プログラミング言語 Go で インスタンスを生成する関数を作るにはどうすれば良いかを調査しました。

抽象化されたプログラムを書くには?

抽象化されたコードを書く場面にしばしば出くわすことがあります。 分かりやすい例として、 型そのものの情報を使ってインスタンスを生成する、というのもその一つです。 Javaで言えば以下のようなコードです。

 1private <T> T createInstance (Class<T> clazz) {
 2    T obj = null;
 3    try {
 4        // クラス情報を基にインスタンスを生成
 5        obj = (T)(Class.forName(clazz.getName()));
 6    } catch (ClassNotFoundException e) {
 7        e.printStackTrace();
 8    }
 9    return obj;
10}

Goではreflectパッケージを使う

先程のようなコードを Go で書くには、 reflect パッケージを使って、リフレクションをすることになります。

今回は型情報を基にインスタンスを生成する createInstance 関数を作成してみましょう。

関数本体の処理

まずはインスタンス生成処理の本体となる createInstance 関数を作成します。 コードは以下のようになります。

1// インスタンスを作るだけの関数
2func createInstance(typ reflect.Type) interface{} {
3	val := reflect.New(typ).Elem()
4	return val.Interface()
5}

ここでポイントをいくつか紹介します。

引数は reflect.Type

関数への引数は reflect.Type 型にしています。 interface{} 型でも良いのですが、interface{} 型だと引数に何でも入れれてしまうので、 reflect.Type によって型を縛ります。

reflect.New() でインスタンスを生成

reflect.New() 関数では、 reflect.Type 型の引数に与えられたインスタンスを生成します。 次に reflect.Elem() でポインタの指す実体にアクセスできるようにします。

reflect.Interface() で interface{} を返却

最後に Interface() 関数を呼んで、 interface{} 型で返せるようにします。 createInstance() 関数内でキャストできると嬉しいかもしれませんが、 ジェネリクス的なものはGoにはありませんし、動的な型アサーションも難しそうなので、 基本的には、createInstance() 関数の呼び出し元で型アサーションをすることになります。

呼び出し元の処理

作成した createInstance() 関数の呼び出し元の処理は以下のようになります。

1var u User
2obj := createInstance(reflect.TypeOf(u))
3r := obj.(User)

reflect.TypeOf() で型情報を取得する

宣言した変数を reflect.TypeOf() の引数に与えることで、 reflect.Type 型のオブジェクトが取得できます。 これを createInstance() 関数に渡して上げます。

戻り値を型アサーション

戻り値を型アサーションすることで obj 変数が User 型としての定義を持っているかを確認してくれます。 これで、呼び出し元でも User 型として処理を書けるわけです。

所感

Go でインスタンス生成を行う、抽象度の少し高いコードを書くことができました。 ただ、逆に手間がかかってしまう感も否めず、変に共通化を狙わずに、シンプルに処理を書いた方が吉かもしれないなー、と思いました。