Go言語では、合成に対するサポートは埋め込みメカニズムを通じて実現されます。これは、一つの構造体を明示的なフィールド名なしに他の構造体の内部に含める能力です。このアプローチは、オブジェクト指向プログラミング(OOP)の継承を「独自の方法で」モデル化し、言語のシンプルさを維持し、複雑な多重継承に関連する多くの問題を回避できると考えられていました。
開発者は、アーキテクチャを複雑にすることなく、いくつかの基本構造体の動作やインターフェースを拡張したいと考えることがよくあります。Goでは、そのために埋め込みを使用することが多く、内包されたタイプのメソッドやフィールドに直接アクセスできるようになり、親構造体に定義されているかのように振る舞います。しかし、埋め込みには特有の注意点があり、メカニズムを誤って理解することは、名前の衝突、二重埋め込み、メソッドの誤った継承など、予期しないエラーを引き起こす可能性があります。
適切かつ効率的に埋め込みを使用することで、よりクリーンな合成を実現できます。メソッドやフィールドが「1レベルだけ上昇する」こと、埋め込みは「has-a」関係を実現していることを忘れないようにしましょう。名前の衝突を避け、メソッドレシーバーの動作を理解することが大切です。
コードの例:
package main import "fmt" type Engine struct { Power int } func (e Engine) Start() { fmt.Println("Engine started with power", e.Power) } type Car struct { Engine // 埋め込みはエンジンのフィールドではなく Brand string } func main() { c := Car{Engine: Engine{Power: 200}, Brand: "Toyota"} c.Start() // 直接アクセス可能 fmt.Println(c.Power) // フィールドも上昇しています }
主な特徴:
埋め込みが多重継承を実現できますか?
いいえ、埋め込みはOOPのような継承を実現しません、それは合成です。一つの構造体に複数の他の構造体を埋め込むことができますが、メソッドが重複する場合はマージではなく、コンフリクトが発生します。
内包された構造体のフィールドが同じ名前の場合はどうなりますか?
直接アクセスするとコンパイルエラーが発生します: コンパイラはどのフィールドにアクセスすべきか分かりません。内包された構造体の名前を通じてパスを明示的に指定する必要があります。
コードの例:
type A struct {X int} type B struct {X int} type C struct { A B } func main() { c := C{} // c.X = 1 // エラー: あいまいなセレクタ c.A.X = 1 // このようにしなければなりません }
埋め込み時に値/ポインタメソッドは同様に昇格されますか?
いいえ。ポインタレシーバーを持つメソッドは、構造体がポインタを使用している場合にのみ上昇します(c := &Car{})。構造体が値の場合、ポインタレシーバーのメソッドは「上昇」しません。
深くネストされた構造体を持つプロジェクトで、埋め込みが多層継承の模倣に使用されています。初心者がフィールドやメソッドがどの構造体から来たのかわからないため、プロジェクト全体が煩雑になり「魔法的」になります。
長所:
短所:
埋め込みを使用してインターフェースを実装します。例えば、LoggerインターフェースはPrintlnメソッドを持つタイプの埋め込みを通じて実装されており、明示的に文書化され、テストがカバーされています。
長所:
短所: