Menu
Coddy logo textTech

インターフェースの合成

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だけでなく、個別のReaderWriterも満たします。

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.ReadWriterio.Readerio.Writer を組み合わせたものです。

challenge icon

チャレンジ

簡単

インターフェースの合成を実演するメディアプレイヤーシステムを構築しましょう。小さく特化したインターフェースを作成し、それらを組み合わせて、単一の型が満たすことができる、より強力な合成インターフェースを作成します。

コードは2つのファイルに分けて構成します:

  • media.go: 3つのインターフェースと、それらすべてを実装する構造体を定義します:
    • Play() string メソッドを持つ Player インターフェース
    • Record(content string) string メソッドを持つ Recorder インターフェース
    • PlayerRecorder の両方を合成した MediaDevice インターフェース
    • 必要なすべてのメソッドを実装する、Name フィールドを持つ SmartDevice 構造体
  • main.go: 同じ SmartDevice が異なるインターフェース型を通じてどのように使用できるかを示す関数を作成します。以下の3つの関数を記述してください:
    • Player を受け取り、その Play メソッドを呼び出す UsePlayer
    • Recorder とコンテンツ文字列を受け取り、Record を呼び出す UseRecorder
    • MediaDevice とコンテンツ文字列を受け取り、RecordPlay の両方を呼び出す UseMediaDevice
    入力からデバイス名とコンテンツを読み取り、SmartDevice を作成し、3つすべてのインターフェース型を通じてそれを使用する方法を示してください。

以下の入力が提供されます:

  • 1行目: デバイス名
  • 2行目: 録音するコンテンツ

メソッドは以下の形式で文字列を返す必要があります:

  • Play(): [Name] is playing
  • Record(content): [Name] recorded: [content]

例えば、MyPhonevoice memo が与えられた場合、出力は以下のようになります:

MyPhone is playing
MyPhone recorded: voice memo
MyPhone recorded: voice memo
MyPhone is playing

最初の2行は、個別のインターフェース(PlayerRecorder)を通じて使用されるデバイスを示しています。最後の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")
}
quiz icon腕試し

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

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