Menu
Coddy logo textTech

複数の構造体の埋め込み

CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 37/107。

Goでは、複数の構造体を単一の構造体に埋め込むことができ、異なるソースからの振る舞いを組み合わせることができます。これは他の言語における多重継承に似ていますが、Goのコンポジション(合成)アプローチによるものです。

type Logger struct{}

func (l Logger) Log(msg string) string {
    return "LOG: " + msg
}

type Notifier struct{}

func (n Notifier) Notify(msg string) string {
    return "NOTIFY: " + msg
}

type Service struct {
    Name string
    Logger
    Notifier
}

Service 構造体は、両方の埋め込み型からのメソッドにアクセスできるようになりました:

func main() {
    s := Service{Name: "OrderService"}
    fmt.Println(s.Log("started"))      // LOG: started
    fmt.Println(s.Notify("new order")) // NOTIFY: new order
}

複数の構造体を埋め込む際、2つの埋め込み型が同じ名前のメソッドを持っていると、名前の衝突が発生する可能性があります。Goはこの曖昧さを自動的に解決しません。

type A struct{}
func (A) Greet() string { return "Hello from A" }

type B struct{}
func (B) Greet() string { return "Hello from B" }

type Combined struct {
    A
    B
}

func main() {
    c := Combined{}
    // c.Greet() - コンパイルエラー: 曖昧なセレクタ
    fmt.Println(c.A.Greet())  // Hello from A
    fmt.Println(c.B.Greet())  // Hello from B
}

競合が発生した場合は、型名を修飾子として使用して、呼び出したい埋め込み型のメソッドを明示的に指定する必要があります。

challenge icon

チャレンジ

簡単

構造体の埋め込み(struct embedding)を通じて、複数の機能を組み合わせたスマートホームデバイスシステムを構築しましょう。別々のコントローラー型を埋め込むことで、照明の制御と音楽の再生の両方が可能なデバイスを作成します。

コードは以下の3つのファイルに分けて構成します:

  • controllers.go: 異なる機能を提供する2つの独立したコントローラー構造体を作成します:
    • Brightness フィールド(int)を持ち、"Light set to [level]%" を返す SetLight(level int) string メソッドを持つ LightController
    • Volume フィールド(int)を持ち、"Volume set to [level]%" を返す SetVolume(level int) string メソッドを持つ AudioController
    両方のコントローラーは Status() string メソッドも持つ必要があります。これにより、対処が必要な名前の衝突が発生します。LightController.Status()"Brightness: [Brightness]%" を返し、AudioController.Status()"Volume: [Volume]%" を返すようにしてください。
  • device.go: Name フィールドを持ち、LightControllerAudioController の両方を埋め込んだ SmartDevice 構造体を作成します。FullStatus() string というメソッドを追加し、埋め込まれた各型の Status() メソッドを明示的に呼び出すことで曖昧さを解消し、デバイス名と両方のコントローラーのステータスを返すようにします。
  • main.go: 入力からデバイス設定を読み取り、SmartDevice を作成し、昇格されたメソッド(promoted methods)を使用して照明と音量のレベルを設定し、最後に完全なステータスを表示します。

以下の入力が提供されます:

  • 1行目:デバイス名
  • 2行目:初期の明るさレベル(整数)
  • 3行目:初期の音量レベル(整数)
  • 4行目:新しく設定する明るさレベル(整数)
  • 5行目:新しく設定する音量レベル(整数)

FullStatus() メソッドは以下を返す必要があります:

[Name] - [LightController.Status()], [AudioController.Status()]

(埋め込まれた型から昇格された)SetLightSetVolume を呼び出した結果を出力し、その後に完全なステータスを出力してください。

例えば、Living Room Hub50307560 が与えられた場合、出力は以下のようになります:

Light set to 75%
Volume set to 60%
Living Room Hub - Brightness: 75%, Volume: 60%

SetLightSetVolume はメソッドの昇格によって SmartDevice から直接アクセスできますが、Status() は両方の埋め込み型がそのメソッドを持っているため、明示的な指定が必要であることに注意してください。

チートシート

Goでは、単一の構造体の中に複数の構造体を埋め込むことができ、コンポジションを通じて異なるソースからの振る舞いを組み合わせることができます。

type Logger struct{}

func (l Logger) Log(msg string) string {
    return "LOG: " + msg
}

type Notifier struct{}

func (n Notifier) Notify(msg string) string {
    return "NOTIFY: " + msg
}

type Service struct {
    Name string
    Logger
    Notifier
}

Service 構造体は、埋め込まれた両方の型のメソッドにアクセスできます。

s := Service{Name: "OrderService"}
fmt.Println(s.Log("started"))      // LOG: started
fmt.Println(s.Notify("new order")) // NOTIFY: new order

埋め込まれた型が同じ名前のメソッドを持っている場合、名前の衝突が発生します。その場合、どちらの埋め込み型のメソッドを呼び出すかを明示的に指定する必要があります。

type A struct{}
func (A) Greet() string { return "Hello from A" }

type B struct{}
func (B) Greet() string { return "Hello from B" }

type Combined struct {
    A
    B
}

// c.Greet() - compile error: ambiguous selector
c := Combined{}
fmt.Println(c.A.Greet())  // Hello from A
fmt.Println(c.B.Greet())  // Hello from B

自分で試してみよう

package main

import (
	"fmt"
)

func main() {
	// 入力を読み込む
	var name string
	fmt.Scanln(&name)

	var initialBrightness int
	fmt.Scanln(&initialBrightness)

	var initialVolume int
	fmt.Scanln(&initialVolume)

	var newBrightness int
	fmt.Scanln(&newBrightness)

	var newVolume int
	fmt.Scanln(&newVolume)

	// TODO: 指定された名前と初期値でSmartDeviceを作成する

	// TODO: 昇格されたSetLightメソッドを使用して結果を出力する

	// TODO: 昇格されたSetVolumeメソッドを使用して結果を出力する

	// TODO: FullStatus()メソッドを使用して完全なステータスを出力する
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン