Ghi nhớ là một kỹ thuật bạn có thể sử dụng để tăng tốc các phương thức truy cập của mình. Nó lưu trữ kết quả của các phương pháp thực hiện công việc tốn thời gian, công việc chỉ cần thực hiện một lần. Trong Rails, bạn thấy ghi nhớ được sử dụng thường xuyên đến mức nó thậm chí còn bao gồm một mô-đun sẽ cung cấp các phương thức ghi nhớ cho bạn.
Sau đó, điều này đã bị loại bỏ gây tranh cãi vì chỉ sử dụng mẫu ghi nhớ thực sự phổ biến mà tôi sẽ nói đến đầu tiên. Nhưng như bạn sẽ thấy, có một số nơi mà kiểu cơ bản này không hoạt động đúng. Vì vậy, chúng ta cũng sẽ xem xét các mẫu ghi nhớ nâng cao hơn và tìm hiểu một số điều cơ bản về Ruby trong quá trình này!
Ghi nhớ siêu cơ bản
Bạn sẽ thấy mẫu ghi nhớ này mọi lúc trong Ruby:
class User < ActiveRecord::Base
def twitter_followers
# assuming twitter_user.followers makes a network call
@twitter_followers ||= twitter_user.followers
end
end
||=
ít nhiều dịch thành @twitter_followers = @twitter_followers || twitter_user.followers
. Điều đó có nghĩa là bạn sẽ chỉ thực hiện cuộc gọi mạng trong lần đầu tiên bạn gọi twitter_followers
và các lệnh gọi trong tương lai sẽ chỉ trả về giá trị của biến cá thể @twitter_followers
.
Ghi nhớ nhiều dòng
Đôi khi, mã chậm sẽ không vừa trên một dòng mà không gây ra những điều tồi tệ cho nó. Có một số cách để mở rộng mẫu cơ bản để hoạt động với nhiều dòng mã, nhưng đây là cách tôi thích nhất:
class User < ActiveRecord::Base
def main_address
@main_address ||= begin
maybe_main_address = home_address if prefers_home_address?
maybe_main_address = work_address unless maybe_main_address
maybe_main_address = addresses.first unless maybe_main_address
end
end
end
begin...end
tạo một khối mã trong Ruby có thể được coi là một thứ duy nhất, giống như {...}
trong ngôn ngữ kiểu C. Đó là lý do tại sao ||=
hoạt động tốt ở đây như nó đã làm trước đây.
Còn nil thì sao?
Nhưng những mẫu ghi nhớ này có một vấn đề khó chịu, tiềm ẩn. Trong ví dụ đầu tiên, điều gì sẽ xảy ra nếu người dùng không có tài khoản twitter và API người theo dõi twitter trả về nil
? Trong phần thứ hai, điều gì sẽ xảy ra nếu người dùng không có bất kỳ địa chỉ nào và khối trả về nil
?
Mỗi lần chúng tôi gọi phương thức, biến phiên bản sẽ là nil
và chúng tôi sẽ thực hiện lại các lần tìm nạp đắt tiền.
Vì vậy, ||=
có lẽ không phải là cách thích hợp để đi. Thay vào đó, chúng ta phải phân biệt giữa nil
và undefined
:
class User < ActiveRecord::Base
def twitter_followers
return @twitter_followers if defined? @twitter_followers
@twitter_followers = twitter_user.followers
end
end
class User < ActiveRecord::Base
def main_address
return @main_address if defined? @main_address
@main_address = begin
main_address = home_address if prefers_home_address?
main_address ||= work_address
main_address ||= addresses.first # some semi-sensible default
end
end
end
Thật không may, điều này xấu hơn một chút, nhưng nó hoạt động với nil
, false
, và mọi thứ khác. (Để xử lý nil
trường hợp, bạn cũng có thể sử dụng Null Objects và các mảng trống để tránh vấn đề này. Thêm một lý do để tránh nil
!)
Còn các thông số thì sao?
Chúng tôi có một số mẫu ghi nhớ hoạt động tốt cho những người truy cập đơn giản. Nhưng nếu bạn muốn ghi nhớ một phương thức có tham số, như phương thức này thì sao?
class City < ActiveRecord::Base
def self.top_cities(order_by)
where(top_city: true).order(order_by).to_a
end
end
Hóa ra là Hash
của Ruby có một công cụ sinh dục hoạt động hoàn hảo cho tình huống này. Bạn có thể gọi Hash.new
với một khối:
Hash.new {|h, key| h[key] = some_calculated_value }
Sau đó, mỗi khi bạn cố gắng truy cập vào một khóa trong hàm băm chưa được đặt, nó sẽ thực thi khối. Và nó sẽ chuyển chính hàm băm cùng với khóa mà bạn đã cố gắng truy cập vào khối.
Vì vậy, nếu bạn muốn ghi nhớ phương thức này, bạn có thể thực hiện một số việc như:
class City < ActiveRecord::Base
def self.top_cities(order_by)
@top_cities ||= Hash.new do |h, key|
h[key] = where(top_city: true).order(key).to_a
end
@top_cities[order_by]
end
end
Và bất kể bạn chuyển gì vào order_by
, kết quả chính xác sẽ được ghi nhớ. Vì khối chỉ được gọi khi khóa không tồn tại, bạn không phải lo lắng về kết quả khối là nil hoặc false.
Thật tuyệt vời, Hash
hoạt động tốt với các khóa thực sự là mảng:
h = {}
h[["a", "b"]] = "c"
h[["a", "b"]] # => "c"
Vì vậy, bạn có thể sử dụng mẫu này trong các phương thức với bất kỳ số lượng tham số nào!
Tại sao lại phải trải qua những rắc rối này?
Tất nhiên, nếu bạn bắt đầu thêm các mẫu ghi nhớ này vào nhiều phương thức, mã của bạn sẽ trở nên khó đọc khá nhanh. Các phương pháp của bạn sẽ là tất cả các nghi lễ và không có thực chất.
Vì vậy, nếu bạn đang làm việc trên một ứng dụng cần nhiều bản ghi nhớ, bạn có thể muốn sử dụng một viên ngọc có khả năng xử lý bản ghi nhớ cho bạn bằng một API thân thiện và đẹp mắt. Memoist có vẻ là một cái hay, và khá giống với những gì Rails từng có. (Hoặc, với kiến thức ghi nhớ mới tìm thấy của bạn, bạn thậm chí có thể thử tự xây dựng một kiến thức).
Nhưng luôn thú vị khi điều tra các mẫu như thế này, xem cách chúng được kết hợp với nhau, vị trí hoạt động và vị trí của các cạnh sắc nét. Và bạn có thể tìm hiểu một số điều thú vị về một số tính năng ít được biết đến của Ruby trong khi khám phá.