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)"アダプターパターンは、サードパーティライブラリの統合、レガシーコードの操作、またはインターフェースの要件に一致しない既存のクラスを使用する必要がある場合に非常に有用です。これは、元のコードを修正することなく、架け橋として機能します。
チャレンジ
簡単アダプターパターンを使用してメディアプレーヤーシステムを構築しましょう!統一されたインターフェースを期待する最新のオーディオプレーヤーがありますが、全く異なるメソッドシグネチャを使用するレガシーなオーディオ機器を統合する必要があります。アダプターはこのギャップを埋め、古いハードウェアが新しいシステムとシームレスに動作するようにします。
コードは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 45rpmPlayMusic 関数が両方のプレーヤーで全く同じように動作することに注目してください。ビニールプレーヤーが実際には全く異なるインターフェースから適合(アダプト)されていることを、この関数は知りません。アダプターが舞台裏で最新のインターフェース呼び出しをレガシーな形式に変換しているのです!
チートシート
アダプターパターン(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 を使用する際は、この行を削除してください
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー11標準ライブラリとOOP
io.Reader と io.Writersort.Interfacefmt.Stringer インターフェースencoding/json と構造体http.Handler インターフェースまとめ:REST API モデル14デザインパターン パート2
CommandパターンAdapterパターンDecoratorパターンTemplate MethodパターンStateパターンCompositeパターンDecoratorとしてのMiddleware