Menu
CoddyTech

Mẫu Decorator

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 93 trên 107.

Mẫu Decorator cho phép bạn thêm hành vi mới vào các đối tượng một cách linh hoạt bằng cách bọc chúng trong các đối tượng khác. Adapter thay đổi giao diện để phù hợp với giao diện khác, còn Decorator giữ nguyên giao diện nhưng tăng cường chức năng bằng cách thêm các lớp bọc quanh đối tượng ban đầu.

Trong Go, ta triển khai cách này bằng cách cho cả kiểu cơ sở và các decorator cùng triển khai một interface:

type Notifier interface {
    Send(message string) string
}

type BasicNotifier struct{}

func (b BasicNotifier) Send(message string) string {
    return "Sending: " + message
}

Decorator bao bọc một Notifier khác và thêm hành vi trước hoặc sau khi ủy quyền cho nó:

type TimestampDecorator struct {
    wrapped Notifier
}

func (t TimestampDecorator) Send(message string) string {
    timestamped := "[2024-01-15] " + message
    return t.wrapped.Send(timestamped)
}

type UppercaseDecorator struct {
    wrapped Notifier
}

func (u UppercaseDecorator) Send(message string) string {
    return strings.ToUpper(u.wrapped.Send(message))
}

Sức mạnh của Decorator nằm ở việc xếp chồng nhiều lớp bọc để kết hợp các hành vi:

notifier := BasicNotifier{}
withTimestamp := TimestampDecorator{wrapped: notifier}
withBoth := UppercaseDecorator{wrapped: withTimestamp}

fmt.Println(notifier.Send("Hello"))
// Đang gửi: Hello

fmt.Println(withBoth.Send("Hello"))
// ĐANG GỬI: [2024-01-15] HELLO

Mẫu trang trí (decorator) phù hợp khi bạn cần thêm chức năng cho các đối tượng mà không sửa đổi code của chúng, đặc biệt khi cần kết hợp các tính năng khác nhau. Mẫu này thường được dùng cho các lớp ghi nhật ký, lưu vào bộ nhớ đệm, xác thực và nén.

challenge icon

Thử thách

Dễ

Hãy xây dựng hệ thống đặt đồ uống cho quán cà phê bằng mẫu Decorator! Bạn sẽ tạo một đồ uống cà phê cơ bản và các decorator để thêm nguyên liệu như sữa, đường và kem tươi. Mỗi decorator bọc decorator trước đó, dần hoàn thiện cả phần mô tả lẫn giá tiền: đây là ví dụ thực tế hoàn hảo về cách decorator xếp lớp chức năng.

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

  • beverage.go: Định nghĩa interface cốt lõi và kiểu cà phê cơ bản.

    Tạo interface Beverage với hai phương thức:

    • Description() string: trả về tên đồ uống
    • Cost() float64: trả về giá tiền

    Triển khai một struct Coffee làm đồ uống cơ bản. Phần mô tả là Coffee và giá tiền là 2.00.

  • decorators.go: Tạo các kiểu decorator để thêm nguyên liệu vào bất kỳ đồ uống nào.

    Mỗi decorator bọc một Beverage và triển khai cùng interface đó. Tạo ba decorator:

    • MilkDecorator: thêm , Milk vào phần mô tả và 0.50 vào giá tiền
    • SugarDecorator: thêm , Sugar vào phần mô tả và 0.25 vào giá tiền
    • WhipDecorator: thêm , Whip vào phần mô tả và 0.75 vào giá tiền

    Mỗi decorator cần gọi các phương thức của đồ uống được bọc rồi bổ sung vào kết quả.

  • main.go: Tạo đồ uống theo yêu cầu bằng cách xếp chồng các decorator.

    Đọc số lượng nguyên liệu cần thêm. Sau đó, với từng nguyên liệu, đọc loại nguyên liệu (milk, sugar hoặc whip) rồi bọc đồ uống hiện tại bằng decorator phù hợp.

    Sau khi thêm tất cả nguyên liệu, in phần mô tả và giá tiền của đồ uống cuối cùng trên các dòng riêng. Định dạng giá tiền theo dạng $X.XX, với đúng hai chữ số thập phân.

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

  • Dòng 1: Số lượng nguyên liệu cần thêm
  • Các dòng tiếp theo: Mỗi dòng ghi một loại nguyên liệu (milk, sugar hoặc whip)

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

2
milk
sugar

Đầu ra cần là:

Coffee, Milk, Sugar
$2.75

Với đầu vào:

3
whip
milk
milk

Đầu ra cần là:

Coffee, Whip, Milk, Milk
$3.75

Với đầu vào:

0

Đầu ra cần là:

Coffee
$2.00

Lưu ý cách mỗi decorator bọc đồ uống trước đó, dần hoàn thiện cả phần mô tả lẫn giá tiền. Bạn có thể thêm cùng một nguyên liệu nhiều lần, chẳng hạn như thêm gấp đôi sữa, và thứ tự decorator sẽ quyết định thứ tự trong phần mô tả. Cà phê cơ bản không biết gì về các nguyên liệu bổ sung: mỗi decorator chỉ bổ sung vào bất cứ thứ gì nó bọc!

Tự làm thử

package main

import (
	"fmt"
)

func main() {
	// Đọc số lượng thành phần thêm
	var count int
	fmt.Scanln(&count)

	// Bắt đầu với một ly cà phê cơ bản
	var drink Beverage = &Coffee{}

	// Đọc từng thành phần thêm và bọc drink bằng decorator phù hợp
	for i := 0; i < count; i++ {
		var extra string
		fmt.Scanln(&extra)

		// TODO: Dựa vào loại thành phần thêm, bọc drink bằng decorator phù hợp
		// Dùng MilkDecorator cho "milk"
		// Dùng SugarDecorator cho "sugar"
		// Dùng WhipDecorator cho "whip"
	}

	// TODO: In ra mô tả và chi phí cuối cùng
	// Định dạng chi phí thành $X.XX với đúng hai chữ số thập phân
}
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