234. Simplifying the visitor pattern

הכרנו בפעמים הקודמות את הDesign Pattern ששמו Visitor.

בשני המימושים שראינו היו שתי מחלקות:

הVisitor, עם הפונקציה שמקבלת את הElement, האחראי על הפעולה שאנחנו רוצים לבצע

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

והElement, עם הפונקציה שמקבלת את הVisitor, המספר לנו איך לטייל עליו

1
2
3
4
public abstract class CarElement
{
public abstract void Accept(ICarElementVisitor carElementVisitor);
}

מצד אחד זה בסדר, מאחר והפונקציה של הElement מספרת לנו איך לטייל עליו, ואילו הVisitor רק צריך לדעת איזו פעולה להפעיל.

מצד שני, זה קצת מגביל אותנו. אם המחלקות של הElementים הן לא שלנו, ואנחנו רוצים "להוסיף" להן פונקציה וירטואלית, די נתקשה להוסיף את הפונקציה Visit אליהן.


מה אפשר לעשות? נניח לרגע שאנחנו מוותרים על האחריות של הElement לספר לנו איך לטייל עליו.

נניח שהמחלקות שיורשות מCarElement הן לא שלנו, ואנחנו מעוניינים לעשות משהו כזה: אנחנו מעוניינים לכתוב פונקציה בשםToXElement היוצרת מהInstanceים איזשהו Xml המתאר אותן.

אז נוכל לעשות משהו כזה:

ניצור מחלקת Visitor כמו פעם קודמת:

1
2
3
4
5
6
public class CarXElementVisitor
{
public XElement ToXElement(CarElement element)
{
}
}

שתמומש כך:

1
2
3
4
public XElement ToXElement(CarElement element)
{
return InnerToXElement((dynamic) element);
}

כעת לכל טיפוס ניצור Overload מתאים:

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
private XElement InnerToXElement(Wheel wheel)
{
return new XElement("Wheel", new XAttribute("Diameter",
wheel.Diameter));
}
private XElement InnerToXElement(Door door)
{
return new XElement("Door", new XAttribute("Width",
door.Width));
}
private XElement InnerToXElement(Car car)
{
return new XElement("Car",
new XAttribute("Company", car.Company),
new XAttribute("Color", car.Color),
car.Elements.Select(x => ToXElement(x)));
}
private XElement InnerToXElement(CarElement element)
{
return new XElement("UnkownElement",
new XAttribute("Type",
element.GetType().Name));
}

בזמן ריצה מפוענח הOverload המתאים ביותר, אליו ניגשת הפונקציה ToXElement.

אם למשל נכתוב את הקוד הבא, נקבל את הפלט:

1
2
3
4
5
6
7
8
9
10
11
12
CarXElementVisitor visitor = new CarXElementVisitor();
Car car = new Car() { Color = "Red", Company = "Ford" };
car.Elements = new CarElement[]
{
new Wheel() { Diameter = 10 },
new Door() { Width = 23 }
};
XElement result = visitor.ToXElement(car);
//<Car Company="Ford" Color="Red">
// <Wheel Diameter="10" />
// <Door Width="23" />
//</Car>

שזה די מגניב.

שימו לב שהדבר הזה מאפשר לנו "להוסיף" פונקציות וירטואליות גם למחלקות שהן לא שלנו.

עם זאת הפסדנו משהו. אם במימוש הקודם הVisitor לא היה צריך להכיר את המבנה של האובייקט שלנו, עכשיו הוא צריך. (הדבר מתבטא בInnerToXElement של Car).

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

יום דינאמי טוב

שתף