360. Implementing an interception framework - Runtime subclassing

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

ראינו בעבר איזשהי שיטה באמצעות Decorator (ראו גם טיפ מספר 353).

באופן דומה לטיפ מספר 349, די מבאס (וקשה!) לכתוב את המימוש של הממשק כך שיתמוך בInterception.

מה שאפשר לעשות זה את הדבר הבא: באופן דומה לטיפ מספר 349, ניצור טיפוס בזמן ריצה שיורש מהטיפוס שלנו (או לחלופין מממש את מהממשק שלנו) ומבצע את הInterception שלנו.

כמו בטיפ מספר 349, ניתן לעשות דבר כזה ע”י שימוש בReflection.Emit.

כמו בטיפ ההוא, אני לא אראה כאן איך מממשים דבר כזה בReflection.Emit, ואראה במקום זאת שימוש בFramework קיים שנקרא DynamicProxy של Castle שיודע לבצע את זה.

נניח שאנחנו מעוניינים בLoggingInterception: בDynamicProxy יש ממשק בשם IInterceptor שנוכל לממש:

1
2
3
4
public interface IInterceptor
{
void Intercept(IInvocation invocation);
}

כאשר IInvocation הוא הממשק של Castle שמייצג קריאה לפונקציה.

זהו הממשק של Castle לInterception (ראו גם טיפ מספר 357)

כעת אנחנו יכולים לממש אותו כאוות נפשנו:

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
public class LoggingInterceptor : IInterceptor
{
private ILog mLog = LogManager.GetLogger("MyLogger");
public void Intercept(IInvocation invocation)
{
mLog.DebugFormat("Entering {0}, with parameters {1}",
invocation.Method,
GetParameters(invocation));
try
{
invocation.Proceed();
mLog.DebugFormat("Exiting {0} with return value: {1}",
invocation.Method,
invocation.ReturnValue);
}
catch (Exception ex)
{
mLog.Error("An error occured on " + invocation.Method, ex);
throw;
}
}
private static string GetParameters(IInvocation call)
{
return string.Join
(",",
call.Method.GetParameters().Select((x, i) =>
new {x.Name, Index = i}).
Select(x => x.Name + ":" + call.Arguments[x.Index]));
}
}

כעת נצטרך ליצור טיפוס ולהתקין עליו את הInterceptor הזה. זה נעשה באופן דומה למה שראינו בטיפ מספר 357:

1
2
3
4
5
6
Bank myBank = new Bank();
ProxyGenerator generator = new ProxyGenerator();
IBank intercepted = generator.CreateInterfaceProxyWithTargetInterface<IBank>(myBank, new LoggingInterceptor());
intercepted.GetAvailableBudget("My Identity"); // Logs!

הערות:

  • הביצועים של שימוש בטכניקה כזאת היא יותר מהירה בעשרות מונים משימוש בContextBoundObject שכבר ראינו אותו
  • אין חובה לעשות Interception לInterface, ניתן גם לטיפוס עצמו: כל עוד הוא לא Sealed, נוכל לעשות Intercept לכל המתודות הוירטואליות שלו.

סופ"ש עם ירושה בזמן ריצה טוב.

שתף