複数の構造体の埋め込み
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
}
競合が発生した場合は、型名を修飾子として使用して、呼び出したい埋め込み型のメソッドを明示的に指定する必要があります。
チャレンジ
簡単構造体の埋め込み(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フィールドを持ち、LightControllerとAudioControllerの両方を埋め込んだSmartDevice構造体を作成します。FullStatus() stringというメソッドを追加し、埋め込まれた各型のStatus()メソッドを明示的に呼び出すことで曖昧さを解消し、デバイス名と両方のコントローラーのステータスを返すようにします。main.go: 入力からデバイス設定を読み取り、SmartDeviceを作成し、昇格されたメソッド(promoted methods)を使用して照明と音量のレベルを設定し、最後に完全なステータスを表示します。
以下の入力が提供されます:
- 1行目:デバイス名
- 2行目:初期の明るさレベル(整数)
- 3行目:初期の音量レベル(整数)
- 4行目:新しく設定する明るさレベル(整数)
- 5行目:新しく設定する音量レベル(整数)
FullStatus() メソッドは以下を返す必要があります:
[Name] - [LightController.Status()], [AudioController.Status()](埋め込まれた型から昇格された)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 はメソッドの昇格によって 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()メソッドを使用して完全なステータスを出力する
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー