🚀

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.Powerc.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.Powerc.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