Null Object Pattern trong Ruby
Bài đăng này đã không được cập nhật trong 7 năm
Có một số trường hợp khi hệ thống phải sử dụng một vài chức năng và một vài trường hợp nó không sử dụng. Giả sử bạn phải thực hiện một class mà nó phải ghi log vào môt file hoặc console. Nhưng điều này chỉ là một tính năng bổ sung và các dữ liệu được ghi phụ thuộc vào cách cấu hình ghi log của bạn.
Nếu có những trường hợp khi một vài module không cần phải ghi lại log thì bạn phải thực hiện việc kiểm tra xem những module này có cần phải thực hiện log hay không trong một khối IF để kiểm tra trong file cấu hình. Như vậy, việc triển khai này không phải là một giải pháp hay. Và Null Object Design Pattern ra đời từ đây.
Null Object Pattern là gì?
Nó cung cấp một null object để thay thế cho trường hợp một instance bị NULL. Thay vì sử dụng một lệnh IF để check một null value, Null Object sẽ phản ánh một mối liên hệ không phải thực hiện - không làm gì cả.
Tại sao phải sử dụng Null Object Pattern
Ở đây chúng ta có 1 ví dụ như sau:
class User
attr_accessor :credit_card, :subscription
def charge
unless subscription.nil?
subscription.charge(credit_card)
end
end
def has_mentoring?
subscription && subscription.has_mentoring?
end
def price
subscription.try(:price) || 0
end
end
Ví dụ trên chúng ta có một method charge kiểm tra điều kiện xem subscription có nil không. Và có sử dụng method price kiểm tra xem nó có tồn tại, nếu không tồn tại sẽ trả về 0. Tất cả những gì chúng ta cần giải quyết ở đây là "No subscription", tức là chúng ta sẽ bỏ đi những đoạn if, unless vô nghĩa để sử dụng một Null Object, nó sẽ giải quyết được vấn đề dùng if unless để check 1 nil value
Một số giải pháp dùng Null Object
1. Giải pháp thứ 1
Tạo một Class Null Object
class NoSubscription
def charge(credit_card)
“No Charge”
end
def price
0
end
def has_mentoring?
false
end
end
Và bây giờ Class User sẽ như thế này:
class User
attr_accessor :credit_card, :subscription
def charge
subscription.charge(credit_card)
end
def has_mentoring?
subscription.has_mentoring?
end
def price
subscription.price
end
def subscription
@subscription ||= NoSubscription.new
end
end
Ở đây chúng ta không còn cần check nil một số điều kiện trước đó và cũng không cần kiểm tra price có tồn tại hay không. Đoạn code trên nhìn có vẻ làm cho code trong sáng hơn. Tuy nhiên ở đây vẫn còn một điều kiện nữa là:
@subscription ||= NoSubscription.new
Và sẽ có giải pháp tốt hơn một chút và sẽ tránh được việc phải kiểm tra điều kiện như trên. Đó là việc thêm vào một Dependency vào code. Nó sẽ đóng gói các điều kiện vào trong class và tránh được một số đoạn code trùng lặp.
2. Giải pháp 2 - Sử dụng Dependency Injection
Chúng ta sẽ loại bỏ đoạn code này:
@subscription ||= NoSubscription.new
Thay vào đó, chúng ta sẽ sử dụng dependency Injection để truyền một NoSubscription vào lúc tạo khởi tạo một User
User.new(NoSubscription.new, CreditCard.new)
Như vậy chúng ta đã loại bỏ được điều kiện ở trong method subscription. Và đoạn code sau khi sử dụng dependency sẽ như sau:
class User
attr_accessor :credit_card, :subscription
def initialize(subscription, credit_card)
@subscription = subscription
@credit_card = credit_card
end
def charge
subscription.charge(credit_card)
end
def has_mentoring?
subscription.has_mentoring?
end
def price
subscription.price
end
end
Trên đây là một số hiểu biết của mình về Null Object Design Pattern và nó là một giải pháp rất là hữu ích. Mình hy vọng nó sẽ giúp ích các bạn tốt hơn trong việc xử lý code để trở thành một coder chuyên nghiệp và có tâm hơn.
All rights reserved