はじめに
12/1から3日連続で、Go初心者向けのやさしい記事を公開しています。
今日のテーマは委譲です。
「Goには継承がないけど委譲があるよ。」と言われて、「委譲って何?!」と思われた人向けに、なるべく簡潔に分かりやすくまとめました。
インターフェースの基本理解が必要なため、自信のない方は前日のアドベントカレンダーに投稿した【Go初心者向けのやさしい記事】Goのインターフェースを10分で学ぼうを見てください。
委譲と埋め込み
委譲とは
委譲とは、ある構造体やインターフェースの機能を別の構造体やインターフェースでも使えるようにする手法です。
メソッドやフィールドの再利用性を高めます。
Goにはクラスが存在せず、構造体がクラスに似た働きをしています。
しかしながら、構造体はクラスではないため継承ができません。
代わりに、「埋め込み」を利用して委譲ができます。
埋め込みとは、別の構造体やインターフェースを入れ子にしてそれらを要素として持つことです。
委譲の実装例
構造体に構造体を埋め込む
一番使うパターンかと思います。
構造体に構造体を埋め込むことで、別の構造体のフィールドやメソッドをそのまま使えます。
下の実装例では、構造体Aが構造体Bに埋め込まれており、構造体Bは構造体AのフィールドfieldAやメソッドHogeを扱うことができます。
type SampleStructA struct {
fieldA string
}
func (a *SampleStructA) Hoge() string {
return "hoge"
}
type SampleStructB struct {
SampleStructA
fieldB string
}
func main() {
a := &SampleStructA{fieldA: "aaa"}
b := &SampleStructB{SampleStructA{fieldA: "aaa"}, "bbb"}
fmt.Println(a) // &{aaa}
fmt.Println(b) // &{{aaa} bbb}
fmt.Println(b.Hoge()) // hoge
}
インターフェースにインターフェースを埋め込む
インターフェースにインターフェースを埋め込むことで、インターフェースを階層関係でまとめることができます。
下の実装例では、インターフェースAとBはインターフェースCに埋め込まれています。
構造体DはAとBのメソッドをどちらも実装しているため、インターフェースAとBとCを全て満たしています。
type SampleInterfaceA interface {
Hoge()
}
type SampleInterfaceB interface {
Fuga()
}
type SampleInterfaceC interface {
SampleInterfaceA
SampleInterfaceB
}
type SampleStructD struct {
}
func (d *SampleStructD) Hoge() string {
return "hoge"
}
func (d *SampleStructD) Fuga() string {
return "fuga"
}
func main() {
d := SampleStructD{}
fmt.Println(d.Hoge()) // hoge
fmt.Println(d.Fuga()) // fuga
}
構造体にインターフェースを埋め込む
構造体にインターフェースを埋め込むことで、構造体はインターフェースで宣言されたメソッドをオーバーライドできます。
下の実装例では、SampleStructBにSampleInterfaceAを埋め込んでいるため、SampleStructBはメソッドHogeをオーバーライドできます。
もちろん、SampleStructAはSampleInterfaceAを満たしているため、SampleStructAもメソッドHogeを実行できます。
type SampleInterfaceA interface {
Hoge() string
}
type SampleStructA struct {
}
func (a *SampleStructA) Hoge() string {
return "hoge"
}
type SampleStructB struct {
SampleInterfaceA
}
func (b *SampleStructB) Hoge() string {
return "hoge!!!"
}
func main() {
a := SampleStructA{}
fmt.Println(a.Hoge()) // hoge
b := SampleStructB{}
fmt.Println(b.Hoge()) // hoge!!!
}
インターフェースを満たすの連鎖
あるインターフェースを満たす構造体を埋め込んだ場合、埋め込んだ構造体もそのインターフェースを満たすことになります。
下の実装例では、SampleInterfaceAを満たすSampleStructAを埋め込んだSampleStructBは、SampleInterfaceAを満たすことにもなります。
したがって、SampleStructBはSampleStructAのHoge()メソッドを実行できます。
type SampleInterfaceA interface {
Hoge() string
}
type SampleStructA struct {
}
func (a *SampleStructA) Hoge() string {
return "hoge"
}
type SampleStructB struct {
SampleStructA
}
func main() {
a := &SampleStructA{}
fmt.Println(a.Hoge()) // hoge
b := &SampleStructB{}
fmt.Println(b.Hoge()) // hoge
}
多重委譲
1つの構造体やインターフェースに複数の構造体やインターフェースを埋め込むことが可能です。
下の実装例では、SampleStructCがSampleStructAとSampleStructBを両方埋め込んでいます。
したがって、SampleStructCは、SampleStructAとSampleStructBのフィールド両方にアクセスができます。
type SampleInterfaceA interface {
Hoge() string
}
type SampleStructA struct {
hoge string
}
type SampleStructB struct {
fuga string
}
type SampleStructC struct {
SampleStructA
SampleStructB
}
func main() {
c := &SampleStructC{}
c.hoge = "hoge"
c.fuga = "fuga"
fmt.Println(c.hoge) // hoge
fmt.Println(c.fuga) // fuga
}
まとめ
- 継承の代わりに「埋め込み」を使って委譲ができるよー
- 埋め込みは単なる構造体やインターフェースの入れ子だよー
- 委譲することでメソッドやフィールドを再利用しやすくできるよー