golangで構造体を定義するときに外部からnewしてほしくないときがあると思います。
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になってしまいます。
func main() {
h := new(hoge.Hoge)
log.Println(h.Name()) // panic: runtime error: invalid memory address or nil pointer dereference
}
解1
golangではエクスポートしていない構造体でも関数の戻り値として使用することができます。
構造対自体をエクスポートせず、コンストラクタの戻り値とすることでnewできない構造体を作ることができます。
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{}}
}
func main() {
// h := new(hoge.hoge) // コンパイルエラー
h := hoge.NewHoge()
log.Println(f.Name())
}
ただしこの方法では不都合なことがあります。それはsliceやchanが外部から作成できないことです。
構造体自体がエクスポートされていないためmakeの引数として渡すことができないからです。
func main() {
s := make([]hoge.hoge, 0) // コンパイルエラー
}
解2
構造体を直接エクスポートせずにインターフェースをエクスポートすることで解1の問題を解決できます。
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{}}
}
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言語 - データ