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, 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


All Rights Reserved