インターフェースの合成
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 31/107。
Goでは、小さなインターフェースを合成(composing)することで、より大きなインターフェースを構築できます。多くのメソッドを持つ1つの巨大なインターフェースを定義するのではなく、新しいインターフェースの定義内に既存のインターフェースを埋め込みます。
type Reader interface {
Read() string
}
type Writer interface {
Write(data string)
}
// ReadWriterは両方のインターフェースを組み合わせます
type ReadWriter interface {
Reader
Writer
}ReadWriterインターフェースは、Read()とWrite()の両方のメソッドを必要とするようになりました。両方のメソッドを実装する型は、ReadWriterだけでなく、個別のReaderとWriterも満たします。
type File struct {
Name string
}
func (f File) Read() string { return "file content" }
func (f File) Write(data string) { fmt.Println("Writing:", data) }
func Process(rw ReadWriter) {
content := rw.Read()
rw.Write(content)
}
func main() {
f := File{Name: "data.txt"}
Process(f) // FileはReadWriterを満たしています
}このコンポジション(合成)のアプローチにより、インターフェースを小さく、焦点が絞られた状態に保つことができます。必要に応じてこれらを組み合わせることで、関数に対して正確なコントラクト(契約)を作成できます。Goの標準ライブラリはこのパターンを広く使用しており、例えば io.ReadWriter は io.Reader と io.Writer を組み合わせたものです。
チャレンジ
簡単インターフェースの合成を実演するメディアプレイヤーシステムを構築しましょう。小さく特化したインターフェースを作成し、それらを組み合わせて、単一の型が満たすことができる、より強力な合成インターフェースを作成します。
コードは2つのファイルに分けて構成します:
media.go: 3つのインターフェースと、それらすべてを実装する構造体を定義します:Play() stringメソッドを持つPlayerインターフェースRecord(content string) stringメソッドを持つRecorderインターフェースPlayerとRecorderの両方を合成したMediaDeviceインターフェース- 必要なすべてのメソッドを実装する、
Nameフィールドを持つSmartDevice構造体
main.go: 同じSmartDeviceが異なるインターフェース型を通じてどのように使用できるかを示す関数を作成します。以下の3つの関数を記述してください:Playerを受け取り、そのPlayメソッドを呼び出すUsePlayerRecorderとコンテンツ文字列を受け取り、Recordを呼び出すUseRecorderMediaDeviceとコンテンツ文字列を受け取り、RecordとPlayの両方を呼び出すUseMediaDevice
SmartDeviceを作成し、3つすべてのインターフェース型を通じてそれを使用する方法を示してください。
以下の入力が提供されます:
- 1行目: デバイス名
- 2行目: 録音するコンテンツ
メソッドは以下の形式で文字列を返す必要があります:
Play():[Name] is playingRecord(content):[Name] recorded: [content]
例えば、MyPhone と voice memo が与えられた場合、出力は以下のようになります:
MyPhone is playing
MyPhone recorded: voice memo
MyPhone recorded: voice memo
MyPhone is playing最初の2行は、個別のインターフェース(Player と Recorder)を通じて使用されるデバイスを示しています。最後の2行は、両方の機能を必要とする合成された MediaDevice インターフェースを通じて使用される様子を示しています。単一の SmartDevice が、必要な両方のメソッドを持っているため、3つすべてのインターフェースを満たしていることに注目してください。
チートシート
Goでは、既存のインターフェースを新しいインターフェース定義の中に埋め込むことで、小さなインターフェースを合成して、より大きなインターフェースを構築することができます。
type Reader interface {
Read() string
}
type Writer interface {
Write(data string)
}
// ReadWriterは両方のインターフェースを組み合わせます
type ReadWriter interface {
Reader
Writer
}埋め込まれたインターフェースのすべてのメソッドを実装する型は、その合成されたインターフェースを満たします:
type File struct {
Name string
}
func (f File) Read() string { return "file content" }
func (f File) Write(data string) { fmt.Println("Writing:", data) }
func Process(rw ReadWriter) {
content := rw.Read()
rw.Write(content)
}
func main() {
f := File{Name: "data.txt"}
Process(f) // FileはReadWriterを満たします
}合成されたインターフェースを実装する型は、個々の埋め込まれたインターフェースもそれぞれ満たします。この合成のアプローチにより、インターフェースを小さく、目的に特化した状態に保ちつつ、関数に対して正確なコントラクト(契約)を作成することができます。
自分で試してみよう
package main
import (
"bufio"
"fmt"
"os"
)
// TODO: Playerを受け取り、そのPlayメソッドを呼び出すUsePlayerを実装してください
// Play()の結果を出力する必要があります
// TODO: Recorderとコンテンツ文字列を受け取るUseRecorderを実装してください
// Record(content)の結果を出力する必要があります
// TODO: MediaDeviceとコンテンツ文字列を受け取るUseMediaDeviceを実装してください
// Record(content)の結果を出力し、その後にPlay()を出力する必要があります
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
deviceName := scanner.Text()
scanner.Scan()
content := scanner.Text()
// TODO: 指定された名前でSmartDeviceを作成してください
// TODO: Playerインターフェースを介してデバイスを使用してください
// TODO: Recorderインターフェースを介してデバイスを使用してください
// TODO: MediaDeviceインターフェースを介してデバイスを使用してください
_ = deviceName
_ = content
fmt.Println("Implement the solution")
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー