233. Making the visitor pattern dynamic

בהמשך לפעם הקודמת, הכרנו קצת את ה Design Pattern ששמוVisitor .

תזכורת: יוצרים איזשהו Class כזה:

1
2
3
4
5
6
7
8
public interface ICarElementVisitor
{
void Visit(Wheel wheel);
void Visit(Door door);
void Visit(Engine engine);
void Visit(Body body);
void Visit(Car car);
}

ובCarElement אנחנו דואגים שתהיה פונקציה כזאת:

1
public abstract void Accept(ICarElementVisitor visitor);

שמספרת לVisitor איך הוא אמור לטייל עלינו.

הבעיה העיקרית במימוש הזה הוא שICarElementVisitor סותר את עקרונות הOOP.

הסיבה לכך היא שהממשק ICarElementVisitorמכיר כבר את כל הטיפוסים היורשים מCarElement, ולכן לא מאפשר לנו הרחבה למימושים אחרים של CarElement.


איך אפשר לתקן?

ראשית נדאג שהממשק ICarElementVisitor יקבל רק את הטיפוס הבסיסי:

1
2
3
4
public interface ICarElementVisitor
{
void Visit(CarElement element);
}

כעת המימוש של הVisitor שלנו ישתמש בDynamic Binding כדי למצוא את הOverload המתאים ביותר:

1
2
3
4
public void Visit(CarElement element)
{
InnerVisit((dynamic)element);
}

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

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
public class CarPhotoShooter : ICarElementVisitor
{
public void Visit(CarElement element)
{
InnerVisit((dynamic)element);
}
private void InnerVisit(Wheel wheel)
{
Console.WriteLine("Taking a photo of the wheel");
}
private void InnerVisit(Door door)
{
Console.WriteLine("Taking a photo of the door");
}
private void InnerVisit(Car car)
{
Console.WriteLine("What a nice car!");
}
// ...
private void InnerVisit(CarElement element)
{
Console.WriteLine("Taking a photo of {0}. I don't really know how to shoot this.",
element.GetType());
}
}

מה שיקרה זה שבזמן ריצה נמצא הOverload המתאים ביותר, כמו שהיה נמצא בזמן קימפול – בטיפ של אתמול, מאחר ובממשק היו הרבה Overloadים, הוא ידע בזמן קימפול לנתב אותנו לOverload המתאים ביותר. עכשיו בזמן ריצה ננותב למתודה המתאימה ביותר.

הדבר הזה בעצם מאפשר לנו "להוסיף" מתודה וירטואלית ולדרוס אותה עבור כל מימוש שנרצה של CarElement, בניגוד לפעם שעברה, שאפשר לנו "להוסיף" מתודה וירטואלית רק לטיפוסים שאנחנו כבר מכירים. (זה לא מדויק, כי יכולנו לעשות כל מיני בדיקות של if (element is Mazda), אבל תסכימו איתי שזה פחות אלגנטי…)

השימוש, כמו בפעם הקודמת, הוא משהו כזה:

1
2
3
ICarElementVisitor visitor = new CarPhotoShooter();
Car car = new Car();
car.Accept(visitor);

המשך יום דינאמי טוב

שתף