🚀
Go構造体埋め込み完全攻略!継承・委譲・フィールド・メソッドをやさしく整理
構造体の埋込などを行って、構造体の中の構造体にアクセスする方法がこんがらがったので、備忘録的に残します。
1.構造体について
そもそも構造体とは(簡単な概要)
golamgには、JavaのようなClassという概念がありません。
その代わり構造体があり、この構造体の中でフィールド(Javaで言うところのメンバ)を宣言します。
また、メソッドはJavaのように構造体(JavaでいうClass)の中に宣言するのではなく、構造体のレシーバ関数として作成します、
type Engine struct {
Power int //←これがフィールド(Javaでいうメンバ)
}
// ↓これがメソッド(Engine型のレシーバ関数)
func (e Engine) Start() {
fmt.Println("Engine started, power:", e.Power)
}
ある構造体が別の構造体を自身の中に含む方法(種類)
構造体の埋込
ある構造体が別の構造体を「名前なしフィールド」として自身の中に含むことを「構造体の埋込」と言います。
type Engine struct {
Power int
}
type Car struct {
Engine // これが埋め込み
Model string
}
埋め込まれた構造体のフィールドやメソッドは、埋め込まれた側のものではなく、外側の構造体のものとして振る舞います。
構造体の名前フィールドとして保存
ある構造体が別の構造体を「名前付きフィールド」として自身の中に含む場合。
type Engine struct {
Power int
}
type Car struct {
EnginePart *Engine // 名前付きフィールド
Model string
}
2. 構造体の中の構造体(フィールド・メソッド)へのアクセスの方法
埋込も2種類の方法で埋め込めるので、それぞれ開設します。
(1) 埋め込みが「値」の場合
type Engine struct {
Power int
}
func (e *Engine) Start() {
fmt.Println("Engine started, power:", e.Power)
}
type Car struct {
Engine
Model string
}
func main() {
c := Car{Engine: Engine{Power: 200}, Model: "Tesla"}
fmt.Println(c.Power) // フィールドに直接アクセス
c.Start() // メソッドも直接呼べる
}
値で埋め込むと、外側の構造体が内部の構造体のコピーを持つため、c.Power
やc.Start()
など直接アクセスできます。
(2) 埋め込みが「ポインタ」の場合
type Engine struct {
Power int
}
func (e *Engine) Start() {
fmt.Println("Engine started, power:", e.Power)
}
type Car struct {
*Engine // ポインタで埋め込み
Model string
}
func main() {
c := Car{Engine: Engine{Power: 200}, Model: "Tesla"}
fmt.Println(c.Power) // フィールドに直接アクセス
c.Start() // メソッドも直接呼べる
}
ポインタで埋め込んでもc.Power
やc.Start()
でアクセス可能ですが、ポインタがnilのときはアクセスはエラーになります。
(3) 名前付きフィールドで保持している場合(埋め込みではない)
type Engine struct {
Power int
}
type Car struct {
EnginePart *Engine // 名前付きフィールド
Model string
}
c := Car{EnginePart: &Engine{Power: 180}}
fmt.Println(c.EnginePart.Power) // 明示的にフィールド名を指定してアクセス
3. 名前の競合時の注意
複数の埋め込み構造体で同名フィールドがあるときは、どれを参照するか明示的に指定が必要です。
type Battery struct {
Capacity int
}
type Engine struct {
Capacity int
}
type Car struct {
Battery
Engine
}
c := Car{
Battery: Battery{Capacity: 500},
Engine: Engine{Capacity: 300},
}
fmt.Println(c.Battery.Capacity) // 500
fmt.Println(c.Engine.Capacity) // 300// fmt.Println(c.Capacity) <-- どちらか曖昧でコンパイルエラーになる
まとめ
埋め込み形態 | アクセス方法 | 備考 |
---|---|---|
値で埋め込み | c.Power |
外側構造体から直接アクセス可能 |
ポインタで埋め込み | c.Power |
ポインタがnilの時はアクセス注意 |
名前付きフィールド保持 | c.EnginePart.Power |
フィールド名を明示的に書く必要あり |
このように、実際の例を身近な構造体名で示すと理解しやすくなります。Go言語の埋め込みは再利用や擬似継承に適した構造化手法です。
Discussion