Design Patterns in Ruby: Observer
Đây là bài viết trong chuỗi bài viết về mẫu thiết kế trong phần mềm và áp dụng của chúng như thế nào vào Ruby. Mẫu thiết kế đầu tiên được giới thiệu là Observer Pattern. Theo wikipedia: The observer pattern (aka. Dependents, publish/subscribe) is a software design pattern in which an object, ...
Đây là bài viết trong chuỗi bài viết về mẫu thiết kế trong phần mềm và áp dụng của chúng như thế nào vào Ruby. Mẫu thiết kế đầu tiên được giới thiệu là Observer Pattern. Theo wikipedia: The observer pattern (aka. Dependents, publish/subscribe) is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. It is mainly used to implement distributed event handling systems. Có thể hiểu Observer là một mẫu thiết kế, trong đó một đối tượng gọi là "subject" có thể quán lý và thông báo đến một list các đối tượng có liên kết đến gọi là "observer" mỗi khi có sự thay đổi trạng thái. Thư viện chuẩn của Ruby cung cấp cho chúng ta một cơ chế cần thiết để có thể thực hiện mẫu thiết kế này, và nó khá dễ dàng để sử dụng. Để hiểu và có thể sử dụng được mẫu thiết kế này, chúng ta hãy đến với một viễn cảnh. Ví dụ là một ứng dụng theo dõi sự vận hành của một chiếc xe và thông báo cho chúng ta biết khi chiếc xe cần phải mang đi bảo hành. Nó là một ví dụ khá đơn giản nhưng cho phép chúng ta có thể áp dụng mẫu thiết kế này.
Cấu trúc cơ bản
Điều đầu tiên là chúng ta sẽ tạo một cấu trúc cơ bản cho lớp Notifier có hành động như một observer. Một hành động chúng ta cần chú ý ở đây là phương thức update(). Hàm này là callback mà Observable sẽ sử dụng khi có thông báo thay đổi observer, và tên của phương thức cần phải là update(). Ta có một class đơn giản là Notifier như sau
class Notifier def update() end end
Tiếp theo chúng ta tạo cấu trúc cho subject là class Car
class Car attr_reader :mileage, :service def initialize(mileage = 0, service = 3000) @mileage, @service = mileage, service end def log(miles) @mileage += miles end end
Bây giờ chúng ta hiểu lớp Car làm gì, chúng ta có thể viết logic cho notifier làm công việc mà chúng đóng vai trò
class Notifier def update(car, miles) puts "The car has logged #{miles} miles, totaling #{car.mileage} miles traveled." puts "The car needs to be taken in for a service!" if car.service <= car.mileage end end
Điều này có nghĩa là mỗi lần notifier được gọi đến, nó sẽ in ra trạng thái thông báo tình trạng của xe đã đi được bao nhiêu và có cần bảo hành hay không.
Kết hợp Observable và Subject
Đầu tiên chúng ta cần đặt observable vào trong class Car
require 'observer' class Car include Observable ... end
Sau đó chúng ta sẽ thêm một observer mỗi lần một thực thể của class Car được tạo. Sửa lại phương thức khởi tạo của class Car như sau
def initialize(mileage = 0, service = 3000) @mileage, @service = mileage, service add_observer(Notifier.new) end
Thay đổi cuối cùng đó là chúng ta cần nói cho observer biết mỗi khi trạng thái của object thay đổi
def log(miles) @mileage += miles changed notify_observers(self, miles) end
Ở đây chúng ta gọi phương thức changed, nó sẽ set giá trị của trạng thái changed cho object(mặc định là true), và notify_observers(self, miles) sẽ thông báo tới obsever một sự thay đổi của subject. Lớp Car hoàn thiện như sau
require 'observer' class Car include Observable attr_reader :mileage, :service def initialize(mileage = 0, service = 3000) @mileage, @service = mileage, service add_observer(Notifier.new) end def log(miles) @mileage += miles changed notify_observers(self, miles) end end
Để tổng kết lại, đây là một số tóm tắt mà chúng ta đã thay đổi class Car
- để sử dụng observer module, chúng ta require nó
- tiếp theo chúng ta khai báo sử dụng nó bằng khai báo include Observable
- khi đối tượng được khởi tạo, chúng ta tạo một observer
- mỗi khi trạng thái của đối tượng thay đổi, chúng ta thông báo thay đổi tới observer
Kết quả chạy
Giờ chúng ta thử chạy và kết quả sẽ được như sau
car = Car.new(2300, 3000) car.log(100) => "The car has logged 100 miles, totaling 2400 miles traveled." car.log(354) => "The car has logged 354 miles, totaling 2754 miles traveled." car.log(300) => "The car has logged 300 miles, totaling 3054 miles traveled." => "The car needs to be taken in for service!"
Refs
Design Patterns in Ruby: Observer, Singleton