Null Object Pattern trong Ruby

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