Loại bỏ Nil value trong Ruby bằng Special Case Pattern
I. Mở đầu Nil value khá là khó chịu, sự hiện diện của nó khiến chúng ta phải làm bừa bộn code của mình với những câu điều điện, cứ phải kiểm tra xem 1 biến có phải nil hay không. Hôm nay mình sẽ dùng 1 cách để loại bỏ nil value trong 1 vài tình huống thường gặp, sử dụng "Special case" pattern. ...
I. Mở đầu
Nil value khá là khó chịu, sự hiện diện của nó khiến chúng ta phải làm bừa bộn code của mình với những câu điều điện, cứ phải kiểm tra xem 1 biến có phải nil hay không. Hôm nay mình sẽ dùng 1 cách để loại bỏ nil value trong 1 vài tình huống thường gặp, sử dụng "Special case" pattern.
II. Ví dụ
Ví dụ dưới đây là 1 helper từ 1 web application, nó tìm current_user bằng cách kiểm tra session object. Nếu nó có thể tìm được thì nó trả về user đó, còn không thì trả về nil
def current_user User.find(session[:user_id]) if session[:user_id] end
Hãy xem xem method này có thể được sử dụng ở đâu:
def greeting "Hello, #{current_user ? current_user.name : 'Guest'}" end
Method trên có thể dùng để chào user khi họ visit page, bằng cách kiểm tra có current_user chưa, nếu có thì dùng name của user đó, không thì dùng từ "Guest"
current_user ? render_logout_button : render_login_button
Đây là trường hợp ta dùng current_user để kiểm tra user đã login chưa để render ra button login, logout tương ứng.
cart = current_user ? current_user.cart : SessionCart.new cart.add_item(item, 1)
Đây là đoạn code dùng để thêm item vào cart, nếu user đã login thì item sẽ được add vào giỏ hàng của user đó, còn không thì nó sẽ được add vào giỏ hàng đặc biệt được lưu tạm thời trong session. Tất cả những đoạn code ở trên đều có 1 điểm chung, đó là nó không chắc chắn về việc sẽ có 1 user object hay không, và nó phải kiểm tra đi kiểm tra lại. Bây giờ để loại bỏ những nil value khó chịu này, ta sẽ viết 1 class thể hiện trường hợp ấy. Ta gọi class đó là GuestUser
class GuestUser def initialize(session) @session = session end end
Ta viết lại method current_user để trả về 1 instance của class này nếu không tìm thấy user_id trong session
def current_user session[:user_id] ? User.find(session[:user_id]) : GuestUser.new(session) end
Ta thêm name attribute vào class GuestUser
class GuestUser # ... def name "Anonymous" end end
Bằng cách đó, ta đơn giản lại method greeting:
def greeting "Hello, #{current_user.name}" end
Đối với trường hợp render login, logout button ta không thể loại bỏ hoàn toàn điều kiện được. Những gì ta có thể làm là thêm 1 method authenticated? để kiểm tra user login chưa trong cả 2 class User và GuestUser
class User def authenticated? true end end class GuestUser def authenticated? false end end
Bằng cách này ta có thể viết lại điều kiện render login, logout button 1 cách ngữ nghĩa hơn:
current_user.authenticated? ? render_logout_button : render_login_button
Bây giờ để implement cart cho users chưa login, ta có thể viết 1 method trong GuestUser để return lại 1 instance của SessionCart
class GuestUser # ... def cart SessionCart.new end end
Giờ đoạn code để add item vào cart ta có thể viết ngắn lại thành current_user.cart.add_item(item, 1) Những gì ta vừa làm chính là nhận dạng 1 "Special Case" - Case mà khi user chưa login. Và sau đó ta có thể diễn tả trường hợp đặc biệt đó như là 1 object bình thường. Kết quả là ta có thể đơn giản đoạn code của mình hơn, và đặc biệt là khiến đoạn code có ngữ nghĩa hơn.