Menu
CoddyTech

複数の構造体の埋め込み

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())  // Aからこんにちは
    fmt.Println(c.B.Greet())  // Bからこんにちは
}

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

challenge icon

チャレンジ

簡単

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

コードを次の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フィールドを持ち、LightControllerとAudioControllerの両方を埋め込むSmartDevice構造体を作成します。デバイス名と両方のコントローラーのステータスを返すFullStatus() stringメソッドを追加します。曖昧さを解消するため、埋め込まれた各型のStatus()メソッドを明示的に呼び出します。
  • main.go:入力からデバイスの設定を読み取り、SmartDeviceを作成し、昇格されたメソッドを使って照明と音量のレベルを設定してから、全体のステータスを表示します。

次の入力が与えられます。

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

FullStatus()メソッドは、次の値を返すようにします。

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

SetLightとSetVolume(埋め込まれた型から昇格されたメソッド)を呼び出した結果を出力し、その後、全体のステータスを出力します。

たとえば、Lounge、50、30、75、60が与えられた場合、出力は次のようになります。

Light set to 75%
Volume set to 60%
Lounge - Brightness: 75%, Volume: 60%

SetLightとSetVolumeはメソッドの昇格によって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オンラインコンパイラ