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] HELLOMẫ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.
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
Beveragevới hai phương thức:Description() string: trả về tên đồ uốngCost() float64: trả về giá tiền
Triển khai một struct
Coffeelàm đồ uống cơ bản. Phần mô tả làCoffeevà 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
Beveragevà triển khai cùng interface đó. Tạo ba decorator:MilkDecorator: thêm, Milkvào phần mô tả và0.50vào giá tiềnSugarDecorator: thêm, Sugarvào phần mô tả và0.25vào giá tiềnWhipDecorator: thêm, Whipvào phần mô tả và0.75và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,sugarhoặcwhip) 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,sugarhoặcwhip)
Ví dụ, với đầu vào:
2
milk
sugarĐầu ra cần là:
Coffee, Milk, Sugar
$2.75Với đầu vào:
3
whip
milk
milkĐầu ra cần là:
Coffee, Whip, Milk, Milk
$3.75Với đầu vào:
0Đầu ra cần là:
Coffee
$2.00Lư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
}
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
1Nền tảng OOP trong Go
Tệp bên ngoàiWorkspace và module trong GoPackage và importTên được export và không được exportGiới thiệu về OOP trong GoStruct như lớpĐịnh nghĩa phương thức cho structReceiver kiểu con trỏ và kiểu giá trịKhởi tạo structHàm khởi tạoÔn tập: máy tính đơn giản4Interface
Giới thiệu về interfaceTriển khai ngầm địnhInterface như một hợp đồngInterface rỗng (any)Khẳng định kiểuswitch kiểuKết hợp interfaceInterface Stringer và ErrorÔn tập: Máy tính hình học7Tính đóng gói
Trường được xuất và không được xuấtĐóng gói ở cấp packagePhương thức getter và setterẨn thông tin trong GoÔn tập: hồ sơ học sinh10Generics (Go 1.18 trở lên)
Giới thiệu về GenericsTham số kiểuRàng buộc kiểuStruct tổng quátGiải pháp thay thế cho phương thức tổng quátÔn tập: tập hợp tổng quát13Các mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếMẫu SingletonMẫu FactoryMẫu Abstract FactoryMẫu ObserverMẫu Strategy2Tìm hiểu chuyên sâu về kiểu dữ liệu và struct
Kiểu cơ bản và kiểu tổng hợpĐịnh nghĩa kiểu dữ liệu tùy chỉnhThẻ structStruct ẩn danhStruct lồng nhauGiá trị zero và giá trị mặc địnhÔn tập: sổ liên lạc5Ưu tiên kết hợp hơn kế thừa
Vì sao Go không có kế thừaNhập môn nhúng structTự động nâng phương thứcNhúng nhiều structNhúng và kết tậpChe khuất phương thức được nhúngÔn tập: hệ phân cấp nhân viên8Xử lý lỗi và lập trình hướng đối tượng
Giao diện errorCác kiểu lỗi tùy chỉnhBọc lỗi với fmt.ErrorfLỗi sentinelerrors.Is() và errors.As()Panic, defer và recoverÔn tập: trình phân tích tệp11Thư viện chuẩn và OOP
io.Reader và io.Writersort.Interfaceinterface fmt.Stringerencoding/json với structinterface http.HandlerÔn tập: mô hình REST API14Mẫu thiết kế phần 2
Mẫu CommandMẫu AdapterMẫu DecoratorMẫu Template MethodMẫu StateMẫu CompositeMiddleware dưới dạng Decorator3Con trỏ và bộ nhớ
Cơ bản về con trỏ trong GoCon trỏ đến structTruyền theo giá trị và tham chiếuHàm new()Thu gom rác trong GoÔn tập: xây dựng danh sách liên kết6Đa hình trong Go
Đa hình qua interfaceĐịnh kiểu vịt trong GoQuy tắc thỏa mãn interfaceCác tập hợp đa hìnhTiêm phụ thuộcÔn tập: bộ xử lý thanh toán9Lập trình đồng thời và lập trình hướng đối tượng
Cơ bản về goroutineChannel và giao tiếpChannel có bộ đệm và không có bộ đệmCâu lệnh selectsync.Mutex và sync.RWMutexsync.WaitGroupThiết kế struct an toàn luồngÔn tập: worker poolTự luyện tập: Trình biên dịch Go online