複数の構造体の埋め込み
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 指定する必要があります。
チャレンジ
簡単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フィールドを持ち、LightControllerとAudioControllerの両方を埋め込むSmartDevicestructを作成します。デバイス名と両方のコントローラーのステータスを返す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された)SetLightとSetVolumeの呼び出し結果を出力し、その後に完全なステータスを出力します。
たとえば、Living Room Hub、50、30、75、60が与えられた場合、出力は次のようになります。
Light set to 75%
Volume set to 60%
Living Room Hub - Brightness: 75%, Volume: 60%SetLightとSetVolumeは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() メソッドを使用して完全なステータスを出力する
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー自分で練習してみよう: Goオンラインコンパイラ