いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-68 (型の組み込みについてのメモ, Embedded, Effective-Go)

概要

よく忘れてしまうので、ここにメモメモ。

Go には、他の言語にあるような継承の概念がありませんが、型の組み込みはサポートされています。

(このあたりはよく議論の的みたいですが、個人的には継承は普段あまり使わないので私は不便ではないです)

組み込みはインターフェースでも構造体でも可能です。

やり方は、型の定義の際に 組み込む対象 の型名をフィールド名をつけずにそのまま記載することです。

インターフェースの場合は

type (
    MyReadWriter interface {
        io.Reader
        io.Writer
    }
)

って風に、そのまんま書くだけです。

構造体の場合も、同じ感じなのですが、ポインタにするかどうかは意識する必要あり。

type (
    helloworld struct {
    }

    embedded struct {
        name        string // 自分のフィールド
        *helloworld        // 組み込み構造体
        *log.Logger        // 組み込み構造体
    }
}

イメージとしては、 Ruby の include mixin に近い感覚。

なので、組み込んだ側は、組み込まれた型に元々定義されているフィールドやメソッドを

あたかも自分で定義したメソッドのように使ってもらうことができます。

上記の場合だと、 *log.Logger を組み込んでいるので、embedded は、何もしなくても

Loggerのメソッドが使えます。

インターフェースの実装具合も同じことで、組み込まれた側の型が元々実装していたインターフェースは

組み込んだ側でも実装済みということになります。よくある委譲のためのコードを書かなくていいのは楽ですね。

上書きの理屈も普通です。組み込んだ側(上でいう embedded)で、組み込まれた側(上でいう *helloworld ) で定義されているメソッドを同名定義すると、組み込んだ側の方が優先です。フィールドも同じ。

あと、組み込まれた型へのアクセス方法ですが、パッケージ名を除いた型名 というルールでフィールドが自動的に定義されるので、それでアクセスします。

上の例でいうと、embedded 内部で組み込まれた型の *helloworld にアクセスしたい場合は

func (e *embedded) f() {
    e.helloworld.xxxx
}

という風にアクセスすることができます。

サンプル

package effectivego24

import (
    "fmt"
    "log"
    "strings"

    "github.com/devlights/gomy/output"
)

type (
    helloworld struct {
    }

    embedded struct {
        name        string // 自分のフィールド
        *helloworld        // 組み込み構造体
        *log.Logger        // 組み込み構造体
    }

    embedded2 struct {
        name      string // 自分のフィールド
        *embedded        // 組み込み構造体
    }
)

func newEmbedded(h *helloworld) *embedded {
    return &embedded{
        name:       "embedded1",
        helloworld: h,
        Logger:     log.New(output.Writer(), "[embedded] ", 0),
    }
}

func newEmbedded2(e *embedded) *embedded2 {
    return &embedded2{
        name:     "embedded2",
        embedded: e,
    }
}

func (h *helloworld) pr() string {
    return "helloworld"
}

func (e *embedded) prName() string {
    h := e.helloworld.pr()
    return strings.ToUpper(h)
}

func (e *embedded2) pr() string {
    return "embedded2 pr()"
}

func (e *embedded2) prName() string {
    return fmt.Sprintf("%s -- embedded2", e.embedded.prName())
}

// Embedding -- Effective Go - Embedding の 内容についてのサンプルです。
func Embedding() error {
    /*
       https://golang.org/doc/effective_go.html#embedding

       Go には、他の言語にあるような継承の概念が無いが、型の組み込みはサポートされている。
       組み込みはインターフェースも構造体でも可能。

       構造体の方の組み込みは少しクセがあり、フィールド名を付与せずに型名のみを記載すると
       その型がまるごと組み込まれる仕組みとなっている。そのため、組み込む側の構造体で
       委譲するようなメソッド定義を行う必要がなくなる。

       当然、組み込んだ型は組み込まれた型が元々実装していたインターフェースも
       自動的に実装していることになる。

       よく利用されるシーンとして、*log.Logger を組み込みにしてしまうことで
       あたかも、その構造体がLoggerのように振る舞うことが出来る。

       組み込んでいる側のメソッドで、組み込んだ型に対してアクセスしたい場合は
       パッケージ名を除いた型名でアクセスすることが出来る。

           例: *log.Logger を組み込んでいる場合は Logger でアクセスできる

       組み込んだ型側にて予め定義されているフィールド、および、メソッドと同名の定義を
       行った場合、親側、つまり、組み込んでいる側の方が優先される。
       つまり、同名の定義を行うことで、元の定義を隠すことになる。
   */
    e := newEmbedded(&helloworld{})

    // 組み込んだ *helloworld のメソッド
    output.Stdoutl("e.pr()", e.pr())

    // 組み込んだ *log.Logger のメソッド
    e.Println("helloworld")

    // 自身のメソッド
    e.Println(e.prName())

    // ---------------------------------------------------------------------
    // 組み込みの *helloworld が持つメソッドと同名のメソッドを定義している型で試す
    // ---------------------------------------------------------------------
    e2 := newEmbedded2(e)

    // pr() は、元々 *helloworld 側で定義されているが、親側で同名定義しているので隠される
    output.Stdoutl("e2.pr()", e2.pr())
    // prName() は、元々 *embedded 側で定義されているが、親側で同名定義しているので隠される
    output.Stdoutl("e2.prName()", e2.prName())

    // 元々のメソッドは当然存在しているので、直接指定すれば勿論呼べる
    output.Stdoutl("e2.helloworld.pr()", e2.helloworld.pr())
    output.Stdoutl("e2.embedded.prName()", e2.embedded.prName())

    return nil
}

try-golang/effectivego_24_embedding.go at master · devlights/try-golang · GitHub

実行すると以下のようになります。

$ make run
ENTER EXAMPLE NAME: effectivego_24_embedding
[Name] "effectivego_24_embedding"
e.pr()               helloworld
[embedded] helloworld
[embedded] HELLOWORLD
e2.pr()              embedded2 pr()
e2.prName()          HELLOWORLD -- embedded2
e2.helloworld.pr()   helloworld
e2.embedded.prName() HELLOWORLD


[Elapsed] 994.9µs

参考

golang.org


過去の記事については、以下のページからご参照下さい。

  • いろいろ備忘録日記まとめ

devlights.github.io

サンプルコードは、以下の場所で公開しています。

  • いろいろ備忘録日記サンプルソース置き場

github.com

github.com

github.com