Menu
Coddy logo textTech

어댑터 패턴

Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 92번째.

어댑터 패턴(Adapter pattern)은 기존 타입을 새로운 인터페이스로 래핑하여 호환되지 않는 인터페이스들이 함께 작동할 수 있도록 합니다. 커맨드(Command)가 동작을 캡슐화하는 반면, 어댑터는 하나의 인터페이스를 클라이언트가 기대하는 다른 인터페이스로 변환합니다.

애플리케이션이 기대하는 것과 다른 메서드 시그니처를 사용하는 레거시 프린터가 있다고 가정해 보겠습니다:

// 애플리케이션이 사용하는 타겟 인터페이스
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

챌린지

쉬움

어댑터 패턴을 사용하여 미디어 플레이어 시스템을 구축해 봅시다! 통합된 인터페이스를 기대하는 현대적인 오디오 플레이어가 있지만, 완전히 다른 메서드 시그니처를 사용하는 레거시 오디오 장비를 통합해야 합니다. 여러분의 어댑터는 이 간극을 메워 구형 하드웨어가 새로운 시스템과 원활하게 작동하도록 할 것입니다.

코드는 세 개의 파일로 구성됩니다:

  • 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와 호환되지 않습니다. 두 개의 매개변수가 필요하며 완전히 다른 메서드 이름을 사용하기 때문입니다.

  • 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" {
		// 바이닐(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실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨