277. Freezing mutable objects

התחלתי ללמוד לא מזמן Ruby, כך שהדבר אולי ישפיע על הפינה.

בRuby בניגוד לJava וC# מחרוזות הן Mutable, כלומר ניתן לשנות את ערכיהן.

למשל, קוד בסגנון הזה יעבוד בRuby:

1
2
3
string helloWorld = "hello World";
helloWorld[0] = "H";
Console.WriteLine(helloWorld); // Hello World

אבל רגע! אמרנו שחשוב שאובייקטים יהיו Immutable, בין השאר כדי שיהיו מפתחות של Dictionary.

אז איך מסתדרים החבר’ה שם בRuby?

ובכן, יש אפשרות להקפיא מחרוזות (ואובייקטים נוספים שהם Mutable) ע"י קריאה למתודה בשם Freeze.

המתודה הזאת גורמת לכך שאם ננסה לשנות את האובייקט, ייזרק לנו Exception. למשל, זה יכול להיות מימוש של אובייקט עם מתודת Freeze בC#:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public class Person
{
private string mName;
private string mLastName;
private int mAge;
private bool mIsFrozen = false;
public string Name
{
get
{
return mName;
}
set
{
if (mIsFrozen)
{
throw new ArgumentException("The object is frozen.");
}
mName = value;
}
}
public string LastName
{
get
{
return mLastName;
}
set
{
if (mIsFrozen)
{
throw new ArgumentException("The object is frozen.");
}
mLastName = value;
}
}
public int Age
{
get
{
return mAge;
}
set
{
if (mIsFrozen)
{
throw new ArgumentException("The object is frozen.");
}
mAge = value;
}
}
public void Freeze()
{
mIsFrozen = true;
}
public bool IsFrozen
{
get
{
return mIsFrozen;
}
}
}

ברגע שאובייקט מוקפא, הוא נהיה Immutable – אי אפשר יותר לשנות אותו.

שימו לב – אין דרך "להפשיר" אובייקט ברגע שהקפאנו אותו!

כך הוא יכול לשמש כמפתח בDictionary וכו’ בלי חשש שישתנה.

למעשה, הDictionary בRuby (מה שנקרא Hash), דואג לכך שאם הוא מקבל כמפתח מחרוזת שהיא לא קפואה (ע"י בדיקת הפונקציה frozen?, אצלנו זה הProperty ששמו IsFrozen), הוא משכפל אותה ומקפיא אותה.

רגע, ואם בא לנו "להפשיר" את האובייקט?

אז Ruby מספקת לנו שתי מתודות: dup וclone:

שתיהן משכפלות את האובייקט, רק שאחת יוצרת אובייקט חדש שהוא כבר לא קפוא…

דיברתי פה הרבה על Ruby, אבל אפשר להעתיק את הקונספט גם לC#: אפשר ליצור ממשק פשוט כזה:

1
2
3
4
5
6
7
8
9
public interface IFreezable : ICloneable
{
void Freeze();
bool IsFrozen
{
get;
}
}

ואז ליצור מחלקת Dictionary משלנו שמקבל מפתחות שהם IFreezableשדואג לקרוא לClone ואז לFreeze ברגע שמכניסים מפתח שהוא לא מוקפא.

ההגדרה של המחלקה היא משהו כזה:

1
2
3
4
public class FreezingDictionary<TKey, TValue> :
IDictionary<TKey, TValue> where TKey : IFreezable
{
}

שבוע קפוא בלי הרבה שינויים טוב!

שתף