Menu
Coddy logo textTech

複数の構造体の埋め込み

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

Goでは、1つの構造体内に複数の構造体を埋め込み、異なるソースの振る舞いを組み合わせることができます。これは他の言語における多重継承に似ていますが、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: 開始
    fmt.Println(s.Notify("new order")) // NOTIFY: 新規注文
}

複数の構造体を埋め込む場合、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
}

conflict が発生した場合は、どの embedded 型の method を call したいのかを、型名を修飾子として使って explicitly 指定する必要があります。

challenge icon

チャレンジ

簡単

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

コードは3つのファイルに分けて整理します。

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

次のinputが提供されます。

  • 1行目: デバイス名
  • 2行目: 初期Brightnessレベル(整数)
  • 3行目: 初期Volumeレベル(整数)
  • 4行目: 設定する新しいBrightnessレベル(整数)
  • 5行目: 設定する新しいVolumeレベル(整数)

FullStatus()メソッドは次を返す必要があります。

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

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

たとえば、Living Room Hub50307560が与えられた場合、出力は次のようになります。

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

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

自分で試してみよう

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腕試し

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

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

自分で練習してみよう: Goオンラインコンパイラ