7.4 埋め込みと継承の違い についての考察

Go言語の埋め込みに関連して以下のような記述がある。

`
具象型を別の具象型に埋め込んでも、外側（上位）の型を内側の埋め込まれた型として扱うことは出来ないが、埋め込まれたフィールドのメソッドは上位の構造体のメソッドセットに含まれるようになる。したがって、上位の構造体は下位の構造体を使ってインタフェースを実装できることになる
`

これについて具体的におさらいする。

埋め込みとは、構造体のフィールドに型名だけを`匿名で`記述し、そのフィールドが持つメソッドとフィールドを外側の構造体から直接呼べるようにする”`委譲`による合成”の仕組み、のこと。この際、`継承`は起こらないが、メソッドだけが`昇格`する。

例えば以下のようにEngineとCarを考える

```golang
type Engine struct{ hp int }

type Car struct {
    Engine // 匿名フィールドとして埋め込み
}

func main() {
    // 以下エラー
    var e Engine
    var c Car
    // error case 1
    e = c // cannot use c (variable of struct type Car) as Engine value in assignment

    // error case 2
    func foo(en Engine) {
        fmt.Println("test", en.hp)
    }
    foo(c) // cannot use c (variable of struct type Car) as Engine value in argument to foo
    ...
```

一方で、昇格は発生するので、埋め込まれたEngineのメソッドHorsePower()がCarの`メソッドセットに追加`される

```golang
func (e Engine) HorsePower() int { return e.hp }
...
c.HorsePower() // 呼び出し可能
```

その結果、外側の型(上記ではCar)がインタフェースを実装できるようになる

```golang
type HPer interface {
    HorsePower() int
}
...
var _ HPer = Car{} // コンパイル通る
```

実際に使う際には以下のような感じで書ける
```golang
func main() {
    c := Car {
        Engine: Engine {
            hp: 10,
        },
    }

    fmt.Println(c.HorsePower())
}
```


上記は構造体Carの中に構造体Engineを記載したが、インタフェースの型を記載することでも昇格は発生する。

```golang
type Logger interface { Log(msg string) }

type Component struct {
    Logger
}
```

実際に使うコードはこんな感じ
```golang
package main

import "log"
import "fmt"
import "bytes"

type Logger interface {
    Log(msg string)
}

type StdLogger struct{}
func (StdLogger) Log(m string) { log.Println("[STD]", m) }

type LoggerFunc func(string)

func (f LoggerFunc) Log(m string) { f(m) }

type Component struct {
    Logger
    Name string
}

func (c *Component) Work() {
    c.Log("component " + c.Name + " working")
}

func main() {
    stdComp := Component{
        Logger: StdLogger{},
        Name: "prod",
    }
    stdComp.Work() // 2025/06/10 11:02:51 [STD] component prod working

    buf := new(bytes.Buffer)
    testComp := Component{
        Logger: LoggerFunc(func(m string) {
            fmt.Fprintln(buf, m)
        }),
        Name: "test",
    }
    testComp.Work()
    fmt.Println("Captured:", buf.String()) // Captured: component test working
}
```


また、インタフェースにインタフェースの型だけを記載するパターンもある

```
type Full interface {
    io.Reader
    io.Writer
}
```

実際に使うコードはこんな感じ
```golang
package main

import (
    "bytes"
    "fmt"
    "io"
)

type Full interface {
    io.Reader
    io.Writer
}

type Pipe struct {
    io.Reader
    io.Writer
}

func NewPipe() *Pipe {
    pr, pw := io.Pipe()
    return &Pipe{Reader: pr, Writer: pw}
}

func main() {
    var f1 Full = bytes.NewBufferString("hello")

    buf := make([]byte, 5)
    f1.Read(buf)
    fmt.Println("bytes.Buffer => ", string(buf)) // bytes.Buffer =>  hello

    pipe := NewPipe()

    var f2 Full = pipe

    go func() {
        f2.Write([]byte("Gopher"))
    }()

    dst := make([]byte, 6)
    f2.Read(dst)
    fmt.Println("Pipe =>", string(dst)) // Pipe => Gopher
}
```



フィールドに名前付きのメンバを入れるのではなく、型の埋め込みを使うことで、すべてのメソッドが自動的に公開される。同様にインタフェースの自動適合も行われる。ただしこれはメソッド名の衝突やAPI膨張などのリスクを同時に招くので、制御を行いたい場合は名前付きのメンバを"小文字で"入れて(もしくはメソッド名が小文字)、メンバ側に公開メソッドを追加することで対応する必要がある。

埋め込みと昇格をサポートしているのはGoの特徴であり、これによりDIやDecoratorを最小コストで書ける、というGoの強みにつながっている、と言える