353. Implementing interception using decoration

בפעמים הקודמות דיברנו קצת על הקונספט של Interception.

לא כל כך דיברנו על איך אפשר לממש דבר כזה – הראיתי שאפשר לממש Interception ע”י עריכת הקוד עצמו, אבל זה כמובן לא קביל מכמה סיבות:

  1. זה דורש לשנות קוד כדי להוסיף התנהגות
  2. צריך לשנות בכל המתודות ולא רק במתודה אחת

במקום זאת, נראה כעת שיטה למימוש Interception בצורה יותר פשוטה.

השיטה מבוססת על הDesign Pattern ששמו Decorator – (ראו גם טיפ מספר 85).

היא מניחה שאנחנו רוצים לעשות Intercept לממשק / מתודה וירטואלית.

בנוסף, היא מניחה שאנחנו יוצרים את הInstance דרך איזשהו Container/Factory.

נניח שאנחנו מעוניינים לעשות את הInterception שעשינו בפעמים הקודמות שכותב לConsole לפני ואחרי כניסה לפונקציה.

נוכל לעשות זאת בצורה הבאה:

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 BankInterception : IBank
{
private IBank mSource;
public BankInterception(IBank source)
{
mSource = source;
}
public void Deposit(string id, int amount)
{
WriteMethodCall(new MethodCallInfo("Deposit")
{
{"id", id},
{"amount", amount}
});
mSource.Deposit(id, amount);
WriteMethodReturn(new MethodCallInfo("Deposit"));
}
public void Withdraw(string id, int amount)
{
WriteMethodCall(new MethodCallInfo("Withdraw")
{
{"id", id},
{"amount", amount}
});
mSource.Withdraw(id, amount);
WriteMethodReturn(new MethodCallInfo("Withdraw"));
}
public int GetAvailableBudget(string id)
{
WriteMethodCall(new MethodCallInfo("GetAvailableBudget")
{
{"id", id},
});
int result = mSource.GetAvailableBudget(id);
WriteMethodReturn(new MethodCallInfo("GetAvailableBudget")
{
ReturnValue = result
});
return result;
}
private static void WriteMethodCall(MethodCallInfo methodCallInfo)
{
Console.WriteLine("Called {0} with arguments:");
foreach (KeyValuePair<string, object> argument in methodCallInfo)
{
Console.WriteLine("\t{0} : {1}",
argument.Key,
argument.Value);
}
}
private static void WriteMethodReturn(MethodCallInfo methodCallInfo)
{
Console.WriteLine("Returned from {0} with value {1}.",
methodCallInfo.Name,
methodCallInfo.ReturnValue);
}
}

כעת אם מישהו מעוניין לקבל את ההתנהגות הזאת, הוא יעשה זאת כך:

1
2
3
4
5
6
Bank myBank = new Bank();
IBank intercepted = new BankInterception(myBank);
intercepted.Deposit("6a91e9f9813a479db7c4bf549bb91c4d", 300);
// Prints to console the parameters

שימו לב שכפי שהערתי בפעם הקודמת, דרך יותר נכונה לעשות את זה היא משהו בסגנון הזה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public int GetAvailableBudget(string id)
{
MethodCallInfo methodCallInfo =
new MethodCallInfo
("GetAvailableBudget",
mSource,
message => mSource.GetAvailableBudget((string) message.Arguments[0]))
{
{"id", id},
};
Route(methodCallInfo);
return (int)methodCallInfo.ReturnValue;
}
private void Route(MethodCallInfo methodCallInfo)
{
WriteMethodCall(methodCallInfo);
methodCallInfo.Proceed();
WriteMethodReturn(methodCallInfo);
}

כפי שאנחנו רואים, זה מאפשר לעשות איזשהו Interception בלי לשנות את הקוד של המחלקה המקורית.

כמובן, זה לא כל כך נוח לכתוב ככה Interceptionים, אבל במקרים שאנחנו מנסים לעשות שינוי התנהגות לממשק ספציפי, זה מספיק טוב 😃

בהמשך נראה פתרון יותר גנרי (כמו שראינו במקרה של Proxyים)

סופ"ש מיורט לטובה!

שתף