golangで構造体を定義するときに外部からnew
してほしくないときがあると思います。
hoge.go
package hoge
type Fuga struct {
name string
}
func (f *Fuga) Name() string {
return f.name
}
type Hoge struct {
fuga *Fuga
}
func (h *Hoge) Name() string {
return h.fuga.Name()
}
func NewHoge() *Hoge {
return &Hoge{&Fuga{}}
}
上記のようなコードにおいてHoge
をnew
されるとName()
を呼び出すときにpanic
になってしまいます。
main.go
func main() {
h := new(hoge.Hoge)
log.Println(h.Name()) // panic: runtime error: invalid memory address or nil pointer dereference
}
解1
golangではエクスポートしていない構造体でも関数の戻り値として使用することができます。
構造対自体をエクスポートせず、コンストラクタの戻り値とすることでnew
できない構造体を作ることができます。
hoge.go
package hoge
type fuga struct {
name string
}
func (f *fuga) Name() string {
return f.name
}
type hoge struct {
fuga *fuga
}
func (h *hoge) Name() string {
return h.fuga.Name()
}
func NewHoge() *hoge {
return &hoge{&fuga{}}
}
main.go
func main() {
// h := new(hoge.hoge) // コンパイルエラー
h := hoge.NewHoge()
log.Println(f.Name())
}
ただしこの方法では不都合なことがあります。それはslice
やchan
が外部から作成できないことです。
構造体自体がエクスポートされていないためmake
の引数として渡すことができないからです。
main.go
func main() {
s := make([]hoge.hoge, 0) // コンパイルエラー
}
解2
構造体を直接エクスポートせずにインターフェースをエクスポートすることで解1の問題を解決できます。
hoge.go
package hoge
type Namer interface {
Name() string
}
type fuga struct {
name string
}
func (f *fuga) Name() string {
return f.name
}
type hoge struct {
fuga *fuga
}
func (h *hoge) Name() string {
return h.fuga.Name()
}
func NewHoge() Namer {
return &hoge{&fuga{}}
}
main.go
func main() {
s := make([]hoge.Namer, 0)
for i := 0; i <= 10; i++ {
s = append(s, hoge.NewHoge())
}
for _, h := range s {
log.Println(h.Name())
}
}
この方法ではインターフェースを戻り値として使用しているため、メンバ変数に直接アクセスすることができません。
結論
解1,2を書きましたがどちらにもある程度のデメリットがあります。
どちらを採用していもいいのですが、そもそもnew
を許容するという選択もあります。
コンストラクタとなる関数名をわかりやすいものにしておけば直接new
を呼び出してpanic
になるというケースもほとんどないでしょう。
golangのデフォルトパッケージの実装にもそうなっているものがあります。(File
など)
実践Go言語 - データ