12/08/2018, 14:56

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.

0