Chain of responsibility design pattern trong Ruby
Bài đăng này đã không được cập nhật trong 3 năm
Tiếp nối loạt bài viết về Design Pattern trước. Bridfe pattern và Facade 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