[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


Nhận xét

  1. 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

Đăng nhận xét

Bài đăng phổ biến từ blog này

Deploy project Springboot MIỄN PHÍ sử dụng Render

Ứng dụng Mã hóa bất đối xứng (Asymmetric cryptography) vào Chữ ký số (Digital Signature)

API và HTTP - Một số khái niệm cơ bản cần biết về Web (Phần 2)