Menu
Coddy logo textTech

Adapterパターン

CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 92/107。

Adapter パターンは、既存の型を新しいインターフェースでラップすることで、互換性のないインターフェース同士を連携させます。Command がアクションをカプセル化するのに対し、Adapter は一方のインターフェースを、クライアントが期待する別のインターフェースへ変換します。

アプリケーションが想定するものとは異なるメソッドシグネチャを使用するレガシープリンターがあると想像してください:

// アプリケーションが使用するターゲットインターフェース
type Printer interface {
    Print(text string) string
}

// 互換性のないインターフェースを持つレガシープリンター
type OldPrinter struct{}

func (o OldPrinter) PrintDocument(doc string, copies int) string {
    return fmt.Sprintf("Old printer: %s (x%d)", doc, copies)
}

adapter は legacy 型をラップし、期待されるインターフェースを implements します。

type OldPrinterAdapter struct {
    oldPrinter OldPrinter
}

func (a OldPrinterAdapter) Print(text string) string {
    return a.oldPrinter.PrintDocument(text, 1)
}

これで、レガシープリンターは Printer インターフェースを期待するコードとシームレスに動作します。

func PrintMessage(p Printer, msg string) string {
    return p.Print(msg)
}

adapter := OldPrinterAdapter{oldPrinter: OldPrinter{}}
result := PrintMessage(adapter, "Hello")  // "Old printer: Hello (x1)"

Adapterパターンは、サードパーティライブラリを統合するとき、legacyコードを扱うとき、またはインターフェースの要件に合わない既存のクラスを使用する必要があるときに非常に役立ちます。元のコードを変更せずに、橋渡しの役割を果たします。

challenge icon

チャレンジ

簡単

Adapterパターンを使ってメディアプレーヤーシステムを構築しましょう!統一されたインターフェースを期待する modern audio player がありますが、まったく異なるメソッドシグネチャを使用する legacy audio equipment を統合する必要があります。adapter がこの隔たりを橋渡しし、古いハードウェアを新しいシステムでシームレスに動作させます。

コードを3つのファイルに分けて整理します。

  • player.go: target interface と legacy audio device を Define します。

    modern system が期待する AudioPlayer interface をCreateします。track name を受け取り、playback message を返す Play(track string) string method を含めます。

    この interface に一致しない legacy device も Define します。

    • VinylPlayer struct に SpeedRPM field (int) を持たせます。Playing [record] ([side] side) at [speed]rpm を返す DropNeedle(record string, side string) string method があります。

    VinylPlayer の interface は AudioPlayer と incompatible です。2つのパラメーターを必要とし、まったく異なる method name を使用します。

  • adapter.go: legacy device を互換性のあるものにする adapter をCreateします。

    VinylPlayer をラップし、AudioPlayer interface を implements する VinylAdapter struct をBuildします。Play が呼び出されたとき、adapter は vinyl player の DropNeedle method を呼び出し、side parameter にはデフォルトで A を使用します。

    比較のために modern player もCreateします。

    • DigitalPlayer struct: その Play method は Streaming: [track] を返します
  • main.go: 同じ interface を通じて両方の player が動作することを示します。

    interface を使って music を再生する PlayMusic(player AudioPlayer, track string) string function をCreateします。この function は、digital player を使用しているのか、adapted vinyl player を使用しているのかを知る必要も気にする必要もありません。

    player type (digital または vinyl) を読み取り、vinyl の場合は RPM speed も読み取ります。その後、track name を読み取ります。適切な player をCreateし(vinyl には adapter を使用)、PlayMusic function を通じて track を再生し、結果を print します。

次の入力が提供されます。

  • Line 1: Player type (digital または vinyl)
  • Line 2: RPM speed (vinyl の場合のみ)
  • Last line: 再生する Track name

たとえば、次の入力が与えられた場合:

digital
Bohemian Rhapsody

出力は次のようになります。

Streaming: Bohemian Rhapsody

また、次の入力が与えられた場合:

vinyl
33
Abbey Road

出力は次のようになります。

Playing Abbey Road (A side) at 33rpm

さらに、次の入力が与えられた場合:

vinyl
45
Blue Monday

出力は次のようになります。

Playing Blue Monday (A side) at 45rpm

PlayMusic function が両方の player に対して同じように動作することに注目してください。vinyl player が実際にはまったく異なる interface から adapted されていることを知る必要はありません。adapter が背後で modern interface の呼び出しを legacy format に変換しています。

自分で試してみよう

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

// PlayMusicはAudioPlayerインターフェースを使用して音楽を再生します
// この関数は任意のAudioPlayer実装で動作します
// TODO: この関数を実装してplayer.Play(track)を呼び出し、結果を返す
func PlayMusic(player AudioPlayer, track string) string {
	// TODO: この関数を実装する
	return ""
}

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// プレイヤータイプを読み取る
	playerType, _ := reader.ReadString('\n')
	playerType = strings.TrimSpace(playerType)
	
	var player AudioPlayer
	var track string
	
	if playerType == "vinyl" {
		// ビニールのRPM速度を読み取る
		rpmStr, _ := reader.ReadString('\n')
		rpmStr = strings.TrimSpace(rpmStr)
		rpm, _ := strconv.Atoi(rpmStr)
		
		// トラック名を読み取る
		track, _ = reader.ReadString('\n')
		track = strings.TrimSpace(track)
		
		// TODO: 指定されたRPMでVinylPlayerを作成する
		// TODO: それをVinylAdapterでラップする
		// TODO: player変数に代入する
		_ = rpm // rpmを使用するときにこの行を削除する
	} else {
		// トラック名を読み取る
		track, _ = reader.ReadString('\n')
		track = strings.TrimSpace(track)
		
		// TODO: DigitalPlayerを作成する
		// TODO: player変数に代入する
	}
	
	// TODO: PlayMusic を呼び出して結果を出力する
	_ = player // player を使用したらこの行を削除してください
	_ = track  // track を使用したらこの行を削除してください
}
quiz icon腕試し

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

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

自分で練習してみよう: Goオンラインコンパイラ