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)
}

アダプターはレガシーな型をラップし、期待されるインターフェースを実装します:

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)"

アダプターパターンは、サードパーティライブラリの統合、レガシーコードの操作、またはインターフェースの要件に一致しない既存のクラスを使用する必要がある場合に非常に有用です。これは、元のコードを修正することなく、架け橋として機能します。

challenge icon

チャレンジ

簡単

アダプターパターンを使用してメディアプレーヤーシステムを構築しましょう!統一されたインターフェースを期待する最新のオーディオプレーヤーがありますが、全く異なるメソッドシグネチャを使用するレガシーなオーディオ機器を統合する必要があります。アダプターはこのギャップを埋め、古いハードウェアが新しいシステムとシームレスに動作するようにします。

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

  • player.go: ターゲットインターフェースとレガシーオーディオデバイスを定義します。

    最新システムが期待する AudioPlayer インターフェースを作成します。これには、トラック名を受け取り再生メッセージを返す Play(track string) string メソッドを持たせます。

    また、このインターフェースに適合しないレガシーデバイスを定義します:

    • VinylPlayer 構造体: SpeedRPM フィールド (int) を持ち、DropNeedle(record string, side string) string メソッドを持ちます。このメソッドは Playing [record] ([side] side) at [speed]rpm を返します。

    VinylPlayer のインターフェースは AudioPlayer と互換性がありません。2つのパラメータを必要とし、全く異なるメソッド名を使用しているためです。

  • adapter.go: レガシーデバイスを互換性のあるものにするアダプターを作成します。

    VinylPlayer をラップし、AudioPlayer インターフェースを実装する VinylAdapter 構造体を構築します。Play が呼び出されたとき、アダプターはビニールプレーヤーの DropNeedle メソッドを呼び出し、side パラメータにはデフォルトで "A" を指定する必要があります。

    比較のために最新のプレーヤーも作成します:

    • DigitalPlayer 構造体: その Play メソッドは Streaming: [track] を返します。
  • main.go: 両方のプレーヤーが同じインターフェースを通じて動作することを実証します。

    インターフェースを使用して音楽を再生する関数 PlayMusic(player AudioPlayer, track string) string を作成します。この関数は、デジタルプレーヤーを使用しているのか、アダプターを介したビニールプレーヤーを使用しているのかを知る必要も、気にする必要もありません。

    プレーヤーのタイプ("digital" または "vinyl")を読み取り、ビニールの場合は RPM スピードも読み取ります。次にトラック名を読み取ります。適切なプレーヤーを作成し(ビニールの場合はアダプターを使用)、PlayMusic 関数を通じてトラックを再生し、結果を出力します。

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

  • 1行目: プレーヤーのタイプ("digital" または "vinyl"
  • 2行目: RPM スピード(ビニールの場合のみ)
  • 最終行: 再生するトラック名

例えば、以下の場合:

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 関数が両方のプレーヤーで全く同じように動作することに注目してください。ビニールプレーヤーが実際には全く異なるインターフェースから適合(アダプト)されていることを、この関数は知りません。アダプターが舞台裏で最新のインターフェース呼び出しをレガシーな形式に変換しているのです!

チートシート

アダプターパターン(Adapter pattern)は、既存の型を新しいインターフェースでラップすることにより、互換性のないインターフェース同士を連携させることができます。

アプリケーションが期待するターゲットインターフェースを定義します:

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)
}

レガシーな型をラップし、ターゲットインターフェースを実装するアダプターを作成します:

type OldPrinterAdapter struct {
    oldPrinter OldPrinter
}

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

アダプターを使用して、ターゲットインターフェースを期待するコードでレガシーな型を動作させます:

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

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

アダプターパターンは、サードパーティライブラリの統合、レガシーコードの利用、または元のコードを変更せずに既存のクラスを必要なインターフェースに適合させる場合に役立ちます。

自分で試してみよう

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腕試し

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

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