16
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ZOZOテクノロジーズ #2Advent Calendar 2019

Day 3

【Goのやさしい記事】埋め込みによる委譲を5分で学ぼう

Last updated at Posted at 2019-12-02

はじめに

12/1から3日連続で、Go初心者向けのやさしい記事を公開しています。
今日のテーマは委譲です。

「Goには継承がないけど委譲があるよ。」と言われて、「委譲って何?!」と思われた人向けに、なるべく簡潔に分かりやすくまとめました。

インターフェースの基本理解が必要なため、自信のない方は前日のアドベントカレンダーに投稿した【Go初心者向けのやさしい記事】Goのインターフェースを10分で学ぼうを見てください。

zozo.go

委譲と埋め込み

委譲とは

委譲とは、ある構造体やインターフェースの機能を別の構造体やインターフェースでも使えるようにする手法です。
メソッドやフィールドの再利用性を高めます。

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
}

まとめ

  • 継承の代わりに「埋め込み」を使って委譲ができるよー
  • 埋め込みは単なる構造体やインターフェースの入れ子だよー
  • 委譲することでメソッドやフィールドを再利用しやすくできるよー

参考

16
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?