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コードを扱うとき、またはインターフェースの要件に合わない既存のクラスを使用する必要があるときに非常に役立ちます。元のコードを変更せずに、橋渡しの役割を果たします。
チャレンジ
簡単Adapterパターンを使ってメディアプレーヤーシステムを構築しましょう!統一されたインターフェースを期待する modern audio player がありますが、まったく異なるメソッドシグネチャを使用する legacy audio equipment を統合する必要があります。adapter がこの隔たりを橋渡しし、古いハードウェアを新しいシステムでシームレスに動作させます。
コードを3つのファイルに分けて整理します。
player.go: target interface と legacy audio device を Define します。modern system が期待する
AudioPlayerinterface をCreateします。track name を受け取り、playback message を返すPlay(track string) stringmethod を含めます。この interface に一致しない legacy device も Define します。
VinylPlayerstruct にSpeedRPMfield (int) を持たせます。Playing [record] ([side] side) at [speed]rpmを返すDropNeedle(record string, side string) stringmethod があります。
VinylPlayer の interface は AudioPlayer と incompatible です。2つのパラメーターを必要とし、まったく異なる method name を使用します。
adapter.go: legacy device を互換性のあるものにする adapter をCreateします。VinylPlayerをラップし、AudioPlayerinterface を implements するVinylAdapterstruct をBuildします。Playが呼び出されたとき、adapter は vinyl player のDropNeedlemethod を呼び出し、side parameter にはデフォルトでAを使用します。比較のために modern player もCreateします。
DigitalPlayerstruct: そのPlaymethod はStreaming: [track]を返します
main.go: 同じ interface を通じて両方の player が動作することを示します。interface を使って music を再生する
PlayMusic(player AudioPlayer, track string) stringfunction をCreateします。この function は、digital player を使用しているのか、adapted vinyl player を使用しているのかを知る必要も気にする必要もありません。player type (
digitalまたはvinyl) を読み取り、vinyl の場合は RPM speed も読み取ります。その後、track name を読み取ります。適切な player をCreateし(vinyl には adapter を使用)、PlayMusicfunction を通じて 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 45rpmPlayMusic 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 を使用したらこの行を削除してください
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
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自分で練習してみよう: Goオンラインコンパイラ