[Design Pattern] Phần 3: Decorator Pattern
[Design
Pattern] Phần 3: Decorator Pattern
Trong phần này, chúng ta sẽ tiếp cận
với một mẫu thiết kế được gọi là Decorator Pattern. Mẫu này giúp chúng
ta mở rộng tính năng của một lớp mà không cần phải chỉnh sửa tại chính lớp đó
Ta
sẽ không dài dòng như phần trước nữa mà bắt đầu đi vào ví dụ luôn
Bạn
bước vào một cửa hàng bán đồ ăn nhanh. Tại cửa hàng có 3 thực đơn: Hamburger,
Phở và Bánh mì với giá lần lượt là 1$ 3$ 2$. Bạn chọn Phở vì đã lâu rồi bạn ở
nước ngoài nhưng chưa được ăn món ăn Việt Nam, nên bạn rất thèm. Ông chủ sau khi
hoàn thành tô phở, nhập vào máy “Phở 3$” rồi nhìn bạn chờ thanh toán. Bạn cảm
thấy tô Phở này chưa có nhiều thịt, bạn yêu cầu nhiều thịt hơn. Ông chủ xóa hóa
đơn và nhập lại “Phở + thịt thêm 3.5$”. Nhưng sau đó bạn cảm thấy muốn thêm một
chút ớt, lại yêu cầu ông chủ thêm tí ớt nữa. Ông chủ cau mày, nhưng cũng phải
phục vụ cho bạn. Thế là ổng xóa đi và nhập lại “Phở + thịt thêm, ớt 3.7$”. Ông
chủ lúc này khá bực vì bạn làm cho ổng phải xóa đi và nhập lại hóa đơn liên tục.
Bạn lặng lẽ thanh toán, cầm tô phở rồi quay đi, lòng thầm nghĩ “Giá như có thêm
rau nữa nhỉ!”.
Trước
khi nói ra vấn đề ông chủ gặp phải, ta hãy thiết kế trước để hiểu hơn nhé!
Đầu
tiên chúng ta cần tạo một abstract class có tên là Food nhé
Đây
là class đại diện cho từng món ăn cụ thể. Nó bao gồm getPrice() để biết giá món
ăn, và getName() để biết tên món ăn.
Giờ
ta sẽ làm 3 class Hamburger, Pho va Banh_mi nhé
Bạn
thấy đấy, mỗi món ăn đều có giá cố định của nó. Nhưng thông thường người mua
ngoài việc chọn món ăn thì sẽ chọn thêm một số option của họ, tùy vào số lượng
và loại option thì món ăn đó giá sẽ thay đổi. Ta thử làm Test tình huống lúc
nãy khi mua Phở nhé
Ok.
Giờ bạn cần gọi thêm thịt nhỉ! Phải làm gì đây.
Có
khá nhiều cách cho việc này.
-
Như là bạn có thể tạo các biến boolean cho
từng món ăn như là từng option đặc biệt, biến nào true thì nghĩa là bạn chọn
option đó. Ví dụ class Pho sẽ thêm các biến boolean như beef (thịt), chili (ớt),
leek (hành). Khi bạn chọn thịt thêm thì beef sẽ là true và giá tiền sẽ chỉnh lại
dựa trên các biến đó. Cách này được nhưng khó khăn cho việc mở rộng vì nếu có
thêm một số yêu cầu phụ như thịt heo hay thịt gà thì sẽ phải chỉnh sửa code
-
Hoặc bạn có thể tạo danh sách các yêu cầu
phụ tại mỗi class món ăn, khi muốn thêm đồ ăn thì bạn chỉ cần add đối tượng yêu
cầu phụ vào trong danh sách là được. (Mình không thiết kế đâu vì lười L
)
-
...
Các
cách này đều có điểm chung là sẽ phải thay đổi code của những phương thức. Bạn
có biết rằng trong lập trình hướng đối tượng có một nguyên tắc mà khi bạn code
bạn nên cố gắng tuân theo, đó là Open-Close: open for extension, but closed for
modification. Đại khái là khi bạn mở rộng một tính năng gì đó, thay vì việc chỉnh
sửa code cũ, bạn nên cố gắng tìm cách tạo code mới mở rộng từ code cũ. Điều này
là dễ hiểu vì code cũ của bạn chạy đúng, nếu bạn chỉnh sửa code cũ để cập nhật
tính năng thì có nguy cơ code cũ sẽ bị sai. Mẫu Decorator Pattern mình sắp giới
thiệu sau đây sẽ giúp bạn không cần chỉnh sửa code cũ mà vẫn có thể mở rộng dễ
dàng.
Mình
sẽ giải thích sơ bộ về mẫu này rồi đi vào code sau.
Lấy
ví dụ về Phở nhé. Một đối tượng Pho sẽ có phương thức getPrice() và getName()
Khi
ta muốn gọi thêm Thịt, nó sẽ trông như thế này
Một
đối tượng Beef chứa một đối tượng Pho. Và điều này cũng tương tự khi gọi thêm
yêu cầu phụ khác
Khi
thực hiện phương thức getName() và getPrice(), chúng sẽ thực hiện như sau
Ở
phương thức getPrice() của đối tượng này, tại beef nó sẽ yêu cầu đối tượng bên
trong là Pho trả về giá là 3$ ra, sau đó nó sẽ lấy giá của nó 0.5$ cộng vô 3$
thành 3.5$ và trả ra ngoài. Nguyên tắc ở đây là bên lớp ngoài yêu cầu lớp bên
trong, lớp bên trong yêu cầu lớp bên trong nữa thực hiện, rồi sau đó trả về từ
trong ra ngoài.
Chúng
ta để ý rằng chúng ta không hề thay đổi gì ở lớp Pho, mà mỗi lần yêu cầu, ta sẽ
tạo một đối tượng ngoài bao lấy bên trong, và gọi theo nguyên tắc đệ quy để thực
hiện xử lý. Để cụ thể hơn, chúng ta cùng thực hiện code.
Ta
sẽ tạo một lớp FoodDecorator extends từ lớp Food. Ý nghĩa của nó là khi một đối
tượng như Pho được thể hiện dưới dạng FoodDecorator, điều đó có nghĩa là nó có
thêm yêu cầu phụ. Cách thể hiện của nó như hình trên là một đối tượng bao bọc đối
tượng khác.
Để
bao bọc được thì ta cần phải nhận diện nó thuộc đối tượng nào, nên ta sẽ dùng một
biến food để biết được điều đó.
Hai
phương thức getPrice() và getName() là bắt buộc đối với từng yêu cầu phụ.
Bây
giờ chúng ta sẽ bắt đầu tạo các lớp yêu cầu phụ
Tiếp
tục với Ớt nào
Giờ
ta sẽ test để hiểu hơn
Ta
sẽ thêm yêu cầu phụ là thịt nhé
Ta
sẽ tiếp tục thêm ớt nào
Ta
có cảm thấy rằng food1 vẫn giữ là Phở nhưng hàm getName() và getPrice() đã có sự
thay đổi không. Đúng vậy, nó như việc chúng ta trang trí (bọc) cho đối tượng
food1 bằng các yêu cầu phụ như thịt với ớt vậy, đối tượng vẫn giữ nguyên và hàm
bị thay đổi.
Nó
giống như việc bạn mặc áo khoác ấy, bạn có thể mặc nhiều lớp, và tất nhiên cũng
có thể cởi ra. Trong code thì bạn có thể làm như sau
Bạn
gọi cái mà nó đang bọc là được.
Về
cơ bản, Decorator Pattern là bọc đối tượng chính bằng những thành phần gọi là
Trang Trí. Các phương thức của các đối tượng Trang Trí này áp dụng nguyên tắc đệ
quy để gọi lần lượt từ trong ra ngoài
Sơ
đồ của Decorator Pattern trông sẽ như này này
Cảm ơn các bạn đã đón xem! Thật sự
thì mình biết cách nó thực hiện nhưng không nắm rõ được ý nghĩa sâu xa của mẫu
này nên chỉ giới thiệu cho các bạn cách sử dụng thôi. Các bạn thông cảm nhé.
Do thứ 7 có việc nên chủ nhật mình mới có thời gian làm. Nói thật thì cũng phải mất kha khá thời gian để hiểu nó :( dù hông hiểu hết. Dù sao cũng cám ơn các bạn đã đọc nhé!
Nội dung bài viết thuộc về Lê Công Diễn. Có sự
tham khảo từ sách Design Pattern for Dummies cũng như một số tài liệu khác.
Người viết: Lê Công Diễn
Mang đi nhớ ghi nguồn
Wonderfull,..
Trả lờiXóaỦa , sao mấy bài trước có giới thiệu bài kì sau, ví dụ " kì sau : Obsever,..." .Bài này lại không có , tính bỏ giữa chừng à .... :(( buồn
Quên ghi ấy haha, đang chuẩn bị viết nè
Xóa