+6

Chain of responsibility design pattern trong Ruby

Tiếp nối loạt bài viết về Design Pattern trước. Bridfe patternFacade Pattern bài này mình sẽ giới thiệu về một design pattern nằm trong nhóm behavioral design patterns. đó là Chain of responsibility

Nó là cái gì?

Trong cuốn sách rất hay về Design Pattern Design Patterns: Elements of Reusable Object-Oriented Software có định nghĩa khá dài và cũng khá là khó hiểu. nên theo tìm hiểu của mình thì mình sẽ nói đơn giản hơn là như thế này: Nó là việc cho tách nhỏ riêng biệt một request của người gửi từ người nhận request đó, bằng việc tạo ra nhiều object để handle request đó. Giả sử bạn có 1 một request cần sử lý với nhiều logic. Nếu sử dung if..else quá nhiều thì sẽ quá phức tạp và khó refactor sau này. Vậy nên Chain of responsibility design pattern tạo ra 1 chuổi các handle. Mỗi Handle xử lý một logic khác nhau và với một điệu kiện cụ thể nào đó. Nói cụ thể hơn thì Chain of responsibility dùng để tránh sự liên kết trực tiếp giữa đối tượng gửi request và đối tượng nhận request khi request đó có thể được xử lý bởi hơn 1 đối tượng. Thực hiện móc nối các đối tượng nhận request đó thành một chuỗi và gửi request theo chuỗi đó cho đến khi request đó được xử lý.

Theo doix UML sau:

Handler :

  • định nghĩa 1 interface để xử lý yêu cầu.
  • Gán giá trị cho đối trượng successor ConcreteHandler:
  • Xử lý yêu cầu.
  • Có thể truy cập đối trượng Successor
  • Nếu đối tượng ConcreteHandler không thể xử lý yêu cầu, nó sẽ gửi tới successor của nó.

Hiểu đơn giãn hơn nữa: Giã sử bạn có 1 chuỗi các hành động. khi nhận được request bạn cho A xử lý. Nếu A không thể xử lý nó sẽ gọi successor của nó để xử lý. cứ như thế cho đến khi không còn ConcreteHandle nào xử lỹ nữa.

Trăm lời nói không bằng 1 ví dụ. mình sẽ thực hiện một ví dụ để thể hiện đống chữ phía trên 😄

Đầu tiên chúng ta định nghĩa 1 class User :

class User
  # possible roles: guest, internal_user, admin
  attr_reader :role

  def initialize(role)
    @role = role
  end
end

class RegistrationController; end
class ProfileController; end
class LogsController; end

User có role và một số Controller như trên.

Tiếp theo sẽ tạo class Policy, đây là đối tượng chính. đó là chính Handler như trong đống lý thuyết trên.

class Policy
  attr_reader :successor

  def initialize(successor=nil)
    @successor = successor
  end

  def check_access(controller, user)
    if policy_matches_controller?(controller)
      return process_checking_access(controller, user)
    elsif successor
      successor.check_access(controller, user)
    else
      raise_no_policy(controller)
    end
  end

  def raise_no_policy(controller)
    raise "No policy found for #{controller}"
  end

  def process_checking_access(controller, user)
    raise 'not implemented'
  end

  protected
  def policy_matches_controller?(controller)
    controller.to_s == self.class.to_s.gsub('Policy', '')
  end
end

Class Policy có định nghĩa 1 instance là Successor. đối tượng này sẽ được gọi khi Hanler không thể xử lý request. Method chính thực hiện xử lý request là check_access. accept 2 params là controller và user. Đầu tiên nó thực hiện check có handle được request hay không bằng cách so sánh tên Controller và tên của Class Handler mà không có word "Policy". Nếu không request sẽ được passed qua successor nếu successor tồn tại, nếu không thì sẽ raise lên exepction. Ngược lại nếu xử lý thành công thì sẽ gọi method process_checking_access cần được định nghĩa bên trong từng concreteHandle class.

Giờ thì chúng ta sẽ định nghĩa một vài ConcreteHandle của Policy.

class RegistrationControllerPolicy < Policy
  def process_checking_access(controller, user)
    user.role == 'guest'
  end
end

class ProfileControllerPolicy < Policy
  def process_checking_access(controller, user)
    ['internal_user', 'admin'].include?(user.role)
  end
end

class LogsControllerPolicy < Policy
  def process_checking_access(controller, user)
    user.role == 'admin'
  end
end

Chúng ta sẽ xây dựng dựa trên Policy class. và định nghĩa lại method process_checking_access để xử lý logic cho từng concrete handler.

Giờ thì chúng ta thực hiện nó :

user = User.new('admin')

policy = LogsControllerPolicy.new(
  ProfileControllerPolicy.new(
    RegistrationControllerPolicy.new()
  )
)

puts policy.check_access(ProfileController, user)

Đầu tiên chúng ta định nghĩa 1 user với role là admin. sau đó định nghĩa 1 chuỗi handler Policy, và thực hiện method check_access tại concretehanlder là LogsControllerPolicy nhưng request sẽ chỉ được xử lý tại ProfileControllerPolicy.

Thế là xong, cũng không quá phức tạp phải không mọi người. (yeah!)

Ưu điểm:

  • Giảm kết nối. Thay vì 1 đối tượng có thể xử lý request phải tham chiều đến các đối tượng khác. Nó chỉ cần tham chiếu đến đối tượng tiếp theo.
  • Tăng tính linh hoạt và phân chia trách nhiệm cho tường đối tựơng.
  • Có khả năng thay đổi dây chuyền.
  • Không đảm bảo có đối tượng xử lý yêu cầu.

Khi nào thì sử dụng Pattern này?

Cái này quan trọng đây, vì hiều mà không biết khi nào apply thì chắc chỉ để đọc cho vui 😄. chúng ta sẽ sử dụng trong những trường hợp sau:

  • Có nhiều hơn 1 đối tượng có thể xử lý request đó, nhưng đối tượng cụ thể nào thực hiện request đó lại phụ thuộc vào ngữ cảnh.
  • Muốn gửi request đến một trong số các đối tượng xử lý, những không biết đối tượng đó là đối tượng nào.
  • Tập các đối tượng xử là tập các đối tượng độc lập và có khả năng biến đổi.

=> Hết rồi mọi người. Cảm ơn mọi người đã đọc bài viết. Mình cũng mới tìm hiểu. và đang apply vào một vài Project DEMO. khi nào xong thì sẽ public sharing với mọi người. Đừng ngại góp ý và chia sẽ nhé! Thank you so much!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí