Tôi không biết C# chút nào, vì vậy bất cứ điều gì tôi nói về C# nên được thực hiện với một hạt muối. Tuy nhiên, tôi sẽ cố gắng giải thích những gì diễn ra trong đoạn mã Ruby đó.
class << Cache
Ruby có tên là phương thức singleton. Chúng không liên quan gì đến Mẫu thiết kế phần mềm Singleton, chúng chỉ là các phương thức được định nghĩa cho một và chỉ một đối tượng. Vì vậy, bạn có thể có hai trường hợp của cùng một lớp và thêm các phương thức vào một trong hai đối tượng đó.
Có hai cú pháp khác nhau cho phương thức singleton. Một là chỉ cần tiền tố tên của phương thức với đối tượng, vì vậy def foo.bar(baz)
sẽ xác định phương thức bar
chỉ cho đối tượng foo
. Phương thức khác được gọi là mở lớp singleton và có vẻ giống như định nghĩa một lớp, bởi vì đó cũng là điều xảy ra ngữ nghĩa: các phương thức singleton thực sự sống trong một lớp vô hình được chèn giữa đối tượng và lớp thực tế của nó trong lớp hệ thống cấp bậc.
Cú pháp này trông giống như sau: class << foo
. Điều này mở ra lớp đơn của đối tượng foo
và mọi phương thức được xác định bên trong của thân lớp đó sẽ trở thành một phương thức đơn của đối tượng foo
.
Tại sao điều này được sử dụng ở đây? Vâng, Ruby là một ngôn ngữ hướng đối tượng thuần túy, có nghĩa là mọi thứ, bao gồm cả các lớp là một đối tượng. Bây giờ, nếu các phương thức có thể được thêm vào các đối tượng riêng lẻ, và các lớp là các đối tượng, điều này có nghĩa là các phương thức có thể được thêm vào các lớp riêng lẻ. Nói cách khác, Ruby không cần sự phân biệt nhân tạo giữa các phương thức thông thường và các phương thức tĩnh (đó là một sự gian lận, dù sao đi nữa: chúng không thực sự là phương pháp, chỉ là các thủ tục được tôn vinh). Một phương thức tĩnh trong C# là gì, chỉ là một phương thức thông thường trong lớp đơn lớp của đối tượng lớp.
Tất cả điều này chỉ là một cách giải thích dài mà mọi thứ được xác định giữa class << Cache
và tương ứng end
của nó trở thành static
.
STALE_REFRESH = 1
STALE_CREATED = 2
Trong Ruby, mọi biến bắt đầu bằng chữ in hoa, thực sự là hằng số. Tuy nhiên, trong trường hợp này, chúng tôi sẽ không dịch các trường này thành các trường static const
, mà là một trường enum
, vì đó là cách chúng được sử dụng.
# Caches data received from a block
#
# The difference between this method and usual Cache.get
# is following: this method caches data and allows user
# to re-generate data when it is expired w/o running
# data generation code more than once so dog-pile effect
# won't bring our servers down
#
def smart_get(key, ttl = nil, generation_time = 30.seconds)
Phương pháp này có ba thông số (bốn trên thực tế, chúng ta sẽ thấy chính xác lý do tại sao sau), hai trong số đó là tùy chọn (ttl
và generation_time
). Cả hai đều có giá trị mặc định, tuy nhiên, trong trường hợp ttl
giá trị mặc định không thực sự được sử dụng, nó phục vụ nhiều hơn như một điểm đánh dấu để tìm hiểu xem đối số có được chuyển vào hay không.
30.seconds
là phần mở rộng mà thư viện ActiveSupport
thêm vào lớp Integer
. Nó không thực sự làm bất cứ điều gì, nó chỉ trả về self
. Nó được sử dụng trong trường hợp này chỉ để làm cho định nghĩa phương thức dễ đọc hơn. (Có các phương pháp khác làm điều gì đó hữu ích hơn, ví dụ: Integer#minutes
, trả về self * 60
và Integer#hours
v.v.) Chúng tôi sẽ sử dụng điều này như một dấu hiệu cho thấy loại tham số không được là int
mà là System.TimeSpan
.
# Fallback to default caching approach if no ttl given
return get(key) { yield } unless ttl
Điều này chứa một số cấu trúc phức tạp của Ruby. Hãy bắt đầu bằng cách đơn giản nhất: các công cụ sửa đổi có điều kiện. Nếu một phần tử có điều kiện chỉ chứa một biểu thức, thì điều kiện có thể được nối vào cuối biểu thức. Vì vậy, thay vì nói if a > b then foo end
bạn cũng có thể nói foo if a > b
. Vì vậy, ở trên tương đương với unless ttl then return get(key) { yield } end
.
Điều tiếp theo cũng dễ dàng: unless
chỉ là cú pháp đường cho if not
. Vì vậy, chúng tôi hiện đang ở if not ttl then return get(key) { yield } end
Thứ ba là hệ thống sự thật của Ruby. Trong Ruby, sự thật là khá đơn giản. Trên thực tế, sự sai lệch khá đơn giản và sự thật rơi tự nhiên: từ khóa đặc biệt false
là sai và từ khóa đặc biệt nil
là sai, mọi thứ khác đều đúng. Vì vậy, trong trường hợp này, điều kiện sẽ chỉ chỉ là đúng, nếu ttl
là false
hoặc nil
. false
không phải là một giá trị hợp lý khủng khiếp cho một khoảng thời gian, do đó, chỉ có một thú vị là nil
. Đoạn mã sẽ được viết rõ ràng hơn như sau: if ttl.nil? then return get(key) { yield } end
. Vì giá trị mặc định cho thông số ttl
là nil
, điều kiện này là đúng, nếu không có đối số nào được chuyển cho ttl
. Vì vậy, điều kiện được sử dụng để tìm ra có bao nhiêu đối số mà phương thức được gọi, có nghĩa là chúng ta sẽ không dịch nó như là một điều kiện quá tải mà là một phương thức quá tải.
Hiện tại, trên yield
. Trong Ruby, mọi phương thức đều có thể chấp nhận một khối mã ngầm định như một đối số. Đó là lý do tại sao tôi đã viết ở trên rằng phương pháp thực sự mất bốn đối số, không phải ba. Một khối mã chỉ là một đoạn mã ẩn danh có thể được truyền xung quanh, được lưu trữ trong một biến và được gọi sau này. Ruby kế thừa các khối từ Smalltalk, nhưng khái niệm này có từ năm 1958, đến các biểu thức lambda của Lisp. Khi đề cập đến các khối mã ẩn danh, nhưng ít nhất là bây giờ, khi đề cập đến các biểu thức lambda, bạn nên biết cách đại diện cho tham số phương thức thứ tư tiềm ẩn này: một loại đại biểu, cụ thể hơn là một Func
.
Vì vậy, yield
làm gì? Nó chuyển quyền kiểm soát khối. Về cơ bản nó chỉ là một cách rất thuận tiện để gọi một khối, mà không cần phải lưu trữ nó một cách rõ ràng trong một biến và sau đó gọi nó.
# Create window for data refresh
real_ttl = ttl + generation_time * 2
stale_key = "#{key}.stale"
cú pháp #{foo}
này được gọi là chuỗi suy. Nó có nghĩa là "thay thế mã thông báo bên trong chuỗi với bất kỳ kết quả nào của việc đánh giá biểu thức giữa các dấu ngoặc nhọn". Nó chỉ là một phiên bản rất ngắn gọn của String.Format()
, đó chính xác là những gì chúng tôi sẽ dịch nó sang.
# Try to get data from memcache
value = get(key)
stale = get(stale_key)
# If stale key has expired, it is time to re-generate our data
unless stale
put(stale_key, STALE_REFRESH, generation_time) # lock
value = nil # force data re-generation
end
# If no data retrieved or data re-generation forced, re-generate data and reset stale key
unless value
value = yield
put(key, value, real_ttl)
put(stale_key, STALE_CREATED, ttl) # unlock
end
return value
end
end
Đây là nỗ lực yếu ớt của tôi tại dịch phiên bản Ruby để C#:
public class Cache<Tkey, Tvalue> {
enum Stale { Refresh, Created }
/* Caches data received from a delegate
*
* The difference between this method and usual Cache.get
* is following: this method caches data and allows user
* to re-generate data when it is expired w/o running
* data generation code more than once so dog-pile effect
* won't bring our servers down
*/
public static Tvalue SmartGet(Tkey key, TimeSpan ttl, TimeSpan generationTime, Func<Tvalue> strategy)
{
// Create window for data refresh
var realTtl = ttl + generationTime * 2;
var staleKey = String.Format("{0}stale", key);
// Try to get data from memcache
var value = Get(key);
var stale = Get(staleKey);
// If stale key has expired, it is time to re-generate our data
if (stale == null)
{
Put(staleKey, Stale.Refresh, generationTime); // lock
value = null; // force data re-generation
}
// If no data retrieved or data re-generation forced, re-generate data and reset stale key
if (value == null)
{
value = strategy();
Put(key, value, realTtl);
Put(staleKey, Stale.Created, ttl) // unlock
}
return value;
}
// Fallback to default caching approach if no ttl given
public static Tvalue SmartGet(Tkey key, Func<Tvalue> strategy) =>
Get(key, strategy);
// Simulate default argument for generationTime
// C# 4.0 has default arguments, so this wouldn't be needed.
public static Tvalue SmartGet(Tkey key, TimeSpan ttl, Func<Tvalue> strategy) =>
SmartGet(key, ttl, new TimeSpan(0, 0, 30), strategy);
// Convenience overloads to allow calling it the same way as
// in Ruby, by just passing in the timespans as integers in
// seconds.
public static Tvalue SmartGet(Tkey key, int ttl, int generationTime, Func<Tvalue> strategy) =>
SmartGet(key, new TimeSpan(0, 0, ttl), new TimeSpan(0, 0, generationTime), strategy);
public static Tvalue SmartGet(Tkey key, int ttl, Func<Tvalue> strategy) =>
SmartGet(key, new TimeSpan(0, 0, ttl), strategy);
}
Xin lưu ý rằng tôi không biết C#, tôi không biết NET, tôi đã không kiểm tra này, tôi thậm chí không biết nó có hợp lệ hay không. Hy vọng nó sẽ giúp anyway.
@ Jorg - bạn sẽ giúp tôi nếu tôi đăng một mã cho Ruby để chuyển đổi C# ??? – maliks
câu trả lời hoàn hảo: giải thích tốt + mã – trinalbadger587