Menu
CoddyTech

Mẫu Adapter

Thuộc phần Lập trình hướng đối tượng trong hành trình GO của Coddy. Bài học 92 trên 107.

Mẫu Adapter cho phép các giao diện không tương thích hoạt động cùng nhau bằng cách bọc một kiểu hiện có bằng giao diện mới. Trong khi Command đóng gói các hành động, Adapter chuyển đổi một giao diện sang giao diện khác mà phía sử dụng mong đợi.

Hãy tưởng tượng bạn có một máy in cũ dùng chữ ký phương thức khác với chữ ký mà ứng dụng của bạn mong đợi:

// Interface đích mà ứng dụng sử dụng
type Printer interface {
    Print(text string) string
}

// Máy in cũ có interface không tương thích
type OldPrinter struct{}

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

Bộ chuyển đổi bao bọc kiểu cũ và triển khai giao diện được mong đợi:

type OldPrinterAdapter struct {
    oldPrinter OldPrinter
}

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

Giờ đây, máy in cũ hoạt động liền mạch với code mong đợi giao diện Printer:

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

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

Mẫu Adapter rất hữu ích khi tích hợp thư viện bên thứ ba, làm việc với code cũ hoặc khi bạn cần dùng các lớp hiện có không đáp ứng yêu cầu về giao diện của mình. Mẫu này đóng vai trò như một cầu nối mà không cần sửa đổi code gốc.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống phát media bằng mẫu Adapter! Bạn có một trình phát âm thanh hiện đại cần một giao diện thống nhất, nhưng cần tích hợp thiết bị âm thanh cũ sử dụng chữ ký phương thức hoàn toàn khác. Adapter sẽ kết nối hai bên, giúp phần cứng cũ hoạt động trơn tru với hệ thống mới.

Bạn sẽ sắp xếp code trong ba tệp:

  • player.go: Định nghĩa giao diện đích và một thiết bị âm thanh cũ.

    Tạo giao diện AudioPlayer mà hệ thống hiện đại cần, với phương thức Play(track string) string nhận tên bản nhạc và trả về thông báo phát nhạc.

    Đồng thời, định nghĩa một thiết bị cũ không tương thích với giao diện này:

    • Cấu trúc VinylPlayer có trường SpeedRPM (int). Cấu trúc này có phương thức DropNeedle(record string, side string) string trả về Playing [record] ([side] side) at [speed]rpm

    Giao diện của VinylPlayer không tương thích với AudioPlayer. Nó cần hai tham số và sử dụng tên phương thức hoàn toàn khác.

  • adapter.go: Tạo adapter giúp thiết bị cũ tương thích.

    Tạo cấu trúc VinylAdapter bọc một VinylPlayer và triển khai giao diện AudioPlayer. Khi gọi Play, adapter sẽ gọi phương thức DropNeedle của trình phát đĩa than, dùng A làm giá trị mặc định cho tham số mặt đĩa.

    Đồng thời, tạo một trình phát hiện đại để so sánh:

    • Cấu trúc DigitalPlayer: phương thức Play trả về Streaming: [track]
  • main.go: Minh họa cách cả hai trình phát hoạt động thông qua cùng một giao diện.

    Tạo hàm PlayMusic(player AudioPlayer, track string) string sử dụng giao diện để phát nhạc. Hàm này không cần biết hay quan tâm liệu nó đang dùng trình phát kỹ thuật số hay trình phát đĩa than đã được chuyển đổi bằng adapter.

    Đọc loại trình phát (digital hoặc vinyl), và nếu là vinyl thì đọc thêm tốc độ RPM. Sau đó đọc tên bản nhạc. Tạo trình phát phù hợp, dùng adapter cho vinyl, phát bản nhạc thông qua hàm PlayMusic rồi in kết quả.

Đầu vào sẽ có dạng như sau:

  • Dòng 1: Loại trình phát (digital hoặc vinyl)
  • Dòng 2: Tốc độ RPM (chỉ khi là vinyl)
  • Dòng cuối: Tên bản nhạc cần phát

Ví dụ, với đầu vào:

digital
Bohemian Rhapsody

Kết quả đầu ra là:

Streaming: Bohemian Rhapsody

Với đầu vào:

vinyl
33
Abbey Road

Kết quả đầu ra là:

Playing Abbey Road (A side) at 33rpm

Với đầu vào:

vinyl
45
Blue Monday

Kết quả đầu ra là:

Playing Blue Monday (A side) at 45rpm

Lưu ý rằng hàm PlayMusic hoạt động giống hệt với cả hai trình phát. Hàm không biết rằng trình phát đĩa than thực chất được chuyển đổi từ một giao diện hoàn toàn khác. Adapter âm thầm chuyển các lời gọi giao diện hiện đại sang định dạng cũ!

Tự làm thử

package main

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

// PlayMusic dùng giao diện AudioPlayer để phát nhạc
// Hàm (function) này hoạt động với mọi cách triển khai AudioPlayer
// TODO: Triển khai hàm này để gọi player.Play(track) và trả về kết quả
func PlayMusic(player AudioPlayer, track string) string {
	// TODO: Triển khai hàm này
	return ""
}

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Đọc loại trình phát
	playerType, _ := reader.ReadString('\n')
	playerType = strings.TrimSpace(playerType)
	
	var player AudioPlayer
	var track string
	
	if playerType == "vinyl" {
		// Đọc tốc độ RPM của đĩa than
		rpmStr, _ := reader.ReadString('\n')
		rpmStr = strings.TrimSpace(rpmStr)
		rpm, _ := strconv.Atoi(rpmStr)
		
		// Đọc tên bản nhạc
		track, _ = reader.ReadString('\n')
		track = strings.TrimSpace(track)
		
		// TODO: Tạo VinylPlayer với RPM đã cho
		// TODO: Bọc nó trong VinylAdapter
		// TODO: Gán cho biến (variable) player
		_ = rpm // Xóa dòng này khi bạn dùng rpm
	} else {
		// Đọc tên bản nhạc
		track, _ = reader.ReadString('\n')
		track = strings.TrimSpace(track)
		
		// TODO: Tạo DigitalPlayer
		// TODO: Gán cho biến (variable) player
	}
	
	// TODO: Gọi PlayMusic và in ra kết quả
	_ = player // Xóa dòng này khi bạn dùng player
	_ = track  // Xóa dòng này khi bạn dùng track
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch Go online