[Design Pattern] Phần 2: Observer Pattern
[Design
Pattern] Phần 2: Observer Pattern
Ở phần thứ nhất, ta đã nói về Strategy
Pattern – một dạng pattern cho phép phân tách thuật toán ra khỏi tính kế thừa.
Tại phần thứ hai, ta sẽ làm quen với Observer Pattern, một loại pattern xử lý vấn
đề cập nhật thông tin
Trước
khi đi vào phần thiết kế, ta sẽ lấy một ví dụ để hiểu rõ hơn Observer Pattern
là gì!
Có
ba đế quốc hùng mạnh trên thế giới là Clown, Immortality, và Death. Cả ba vương
quốc này đều căm ghét nhau và muốn thống trị các bên còn lại.
Đế
quốc Clown đang phát triển vũ khí hủy diệt hàng loạt với sức công phá cực mạnh,
làm cho hai đế quốc còn lại cảm thấy lo ngại.
John
là một tình báo viên với nhiệm vụ thu thập tin tức từ phe địch về cho phe ta. John
ban đầu thuộc phe Immortality đi thu thập thông tin từ đế quốc Clown. Tuy nhiên
anh hay tin rằng Death cũng muốn có được thông tin từ Clown. Vì John là một người
không theo phe nào cả, chỉ đi theo lợi ích của anh ta, nên anh ta sẽ đi thu thập
tin tức từ Clown và bán lại thông tin cho cả hai đế quốc còn lại để được hưởng
nhiều vàng hơn.
Để
nhận được tin tức mới từ John, ta có cách như sau:
Hai
đế quốc còn lại sẽ thường xuyên liên lạc John và hỏi xem có tin tức mới hay
chưa. Để có thể nhận được tin tức càng nhanh càng tốt thì cứ 5 phút họ sẽ hỏi một
lần. Nếu John có tin tức mới thì anh ta sẽ trả lời có, còn không thì anh ta sẽ nói
không.
è Cách
này không hiệu quả vì sẽ làm tốn công sức cũng như thời gian kiểm tra tình hình
của John
Một
cách thứ hai mang lại hiệu quả hơn là hai đế quốc không cần phải theo dõi tình
trạng của John, mà chỉ cần khi nào John có tin tức mới, John sẽ báo cho họ biết.
è Cách
này hiệu quả hơn cách trên vì sẽ không tốn thời gian kiểm tra
Ví
dụ trên là một cách hình dung thực tế cho mẫu Observer Pattern. Mẫu này dùng để
giải quyết vấn đề về cập nhật tình hình.
Nói
cũng nhiều rồi, chúng ta bắt đầu thực hiện nào.
Chúng
ta hãy giả sử rằng chúng ta là một người xây dựng Youtube.
Ta
thấy rằng một Channel sẽ có nhiều Subscriber đăng ký Channel đó.
Giả
sử mình lập ra một Channel và có 3 người đăng ký Channel mình là Anna, Steve và
John.
Yêu
cầu chúng ta đặt ra là thiết kế hệ thống sao cho khi Channel upload một video mới
nhất, thì cả 3 Subscriber này đều nhận thông báo
Ta
sẽ bắt đầu thiết kế hệ thống này
Đầu
tiên ta xây dựng Subscriber trước
Subscriber
thì sẽ có tên, và kênh đăng ký.
Ở
đây mình lấy trường hợp đơn giản nhất là chỉ đăng ký 1 kênh và bản thân
Subscriber không là một Channel nhé
Tiếp
theo là tới Channel
Một
Channel sẽ có tên Channel, danh sách các Subscriber, và sẽ thêm một dòng String
để thể hiện video mới nhất. (Tất nhiên một Channel có khá nhiều video nhưng
mình chỉ thiết kế trong phạm vi yêu cầu là thông báo một video mới nhất)
Nào,
giờ chúng ta cần làm việc này
Chúng
ta sẽ tạo hàm update() cho Subscriber. Hàm này dùng để khi nào có sự thay đổi
(video mới), Channel sẽ gọi hàm này để báo cho Subscriber biết.
Tại
Channel, ta sẽ làm một hàm thông báo
Khi
gọi hàm này, các Subscriber sẽ kích hoạt hàm update(), để thông báo cho họ biết
có video mới đã được cập nhật.
Sự
thông báo (thay đổi) này xảy ra khi có video mới xuất hiện. Vậy ta tiếp tục tạo
thêm một hàm upload video mới
Khi
có một video mới, ta sẽ nhận video vào trong videoTitle và thông báo cho các
Subs (Subscribers) có sự thay đổi.
Số
người đăng ký sẽ có thể thay đổi, chúng có thể tăng lên (add) hoặc giảm xuống
(remove). Vì thế chúng ta cần thêm hai hàm add() và remove() để hoàn thiện
Các
thuộc tính nên đặt ở trạng thái private nhé!
Như
thế là xong. Ta sẽ bắt đầu thử chạy chương trình
(Màu
đỏ là do chưa tạo hàm khởi tạo nhé. Mình thường viết test trước để dễ hình dung
trước khi tạo các hàm khởi tạo)
Ở
hàm test này, chúng ta sẽ tạo một Channel có tên là 8techblog. Anna, Steve và
John là 3 Subscriber. Họ sẽ đăng ký cho Channel của mình.
Giờ
mình sẽ tạo các hàm khởi tạo nào
Channel
sẽ gán tên và khởi tạo danh sách Subs rỗng.
Subscriber
chỉ cần gán tên là đủ.
Hàm
subscribe() sẽ gán Channel đăng ký vào, đồng thời Channel sẽ thêm Subscriber
này vô danh sách.
Mình
quay lại hàm test nào
Bây
giờ chúng ta hãy thử upload một video mới nào
(mình
đã thay biến 8techblog thành techblog do sai sót về nguyên tắc đặt tên, hì hì)
Mọi
thứ có vẻ êm xuôi. Thử chạy chương trình nào!
Ổn
rồi, khi upload một video mới thì các Subs đều nhận được thông báo.
Mà
chúng ta cần biết rõ ràng hơn ai sẽ nhận thông báo, và video họ nhận được là
gì. Chúng ta sẽ chỉnh sửa lại một chút ở hàm update()
Chúng
ta sẽ lấy tên của Subscriber, và tiêu đề video từ Channel.
(Dấu
\” dùng để hiện kí hiệu “ khi xuất màn hình. Dùng \ trước để vô hiệu hóa chức
năng của “ trong chuỗi ký tự. Xem kết quả sẽ hiểu!
Bây
giờ chúng ta thử chạy lại chương trình
Phệt
bơ, bơ phệt! Giờ chúng ta thử John unsubscribe xem John có nhận thông báo không
nhé. Chắc chắn chúng ta sẽ làm hàm unsubscribe() rồi
Okay
giờ test thôi nào
Giờ
John không thể nhận thông báo được rồi
Chúng
ta có thể tạo interface để tổng quát hóa cho nó như sau
Subscriber
sẽ là một Observer và có hàm update() dùng để được gọi khi có gì đó mới xuất hiện.
Channel
sẽ tổng quát hóa thành Subject gồm 3 hàm chính:
-
notif() để thông báo cho Observer (khi có
sự thay đổi sẽ gọi hàm notif() này)
-
addObserver() dùng để thêm một Observer
cho danh sách các Observer mà Subject gửi thông báo
-
removeObserver() dùng để xóa Observer chỉ
định trong danh sách Observer mà Subject gửi thông báo
Việc
tạo Interface để tạo khả năng đa hình, giả sử với vòng for trong hàm notif dùng
để thông báo cho các Observer. Ta có thể thông báo cho các đối tượng khác ngoài
Subscriber theo cấu trúc observer.update()
Giả
sử có một đối tượng thuộc kiểu gì đó không phải Subscriber (Ví dụ như
FacebookSubscriber) vẫn có thể đăng ký cho Channel, thì ta vẫn có thể thông báo
được miễn là implement Observer. Kiểu như thế này này
Subscriber
sẽ có interface tổng quát là Observer.
Còn
Channel làm interface tổng quát là Subject trông như thế này này
Nếu
8techblog có một kênh youtube và một kênh blog. Mỗi khi đăng cùng một bài mới,
thì trong lập trình, chúng ta chỉ cần duyệt qua từng kênh (subject) và gọi hàm
subject.notif() là được.
Sơ
đồ sẽ có dạng như sau
Mình
chỉ vẽ một Subject vì mình đang muốn thể hiện một quan hệ One – To – Many. Việc
thêm vào Subject2, Subject3 chỉ là quá trình mở rộng. Còn ở đây mình để trường
hợp tổng quát cho một quan hệ One – To – Many nên chỉ dùng một cái thôi
Cảm ơn các bạn đã đón xem! Bài viết
thứ 3 về Decorator Pattern sẽ ra mắt vào tuần tới! (có thể ==)
Nội dung bài viết thuộc về Lê Công Diễn. Có sự
tham khảo từ video Telusko và một vài trang web khác.
Người viết: Lê Công Diễn
Mang đi nhớ ghi nguồn
Cảm ơn bạn vì bài viết. Nhưng mình không thể nhận biết lớp nào là Interface nên bạn có thể kí hiệu hay giúp mình nhận biết hay không.Thanks!
Trả lờiXóaÔ nào mà có chữ in nghiêng là Interface nhé
Xóa