了解访客模式


16

我有一个代表GUI控件的类层次结构。像这样:

Control->ContainerControl->Form

我必须实现一系列算法,这些算法可以与处理各种任务的对象一起使用,并且我认为“访客”模式将是最干净的解决方案。让我们举个例子,该算法创建对象层次结构的Xml表示。使用“经典”方法,我可以这样做:

public abstract class Control
{
    public virtual XmlElement ToXML(XmlDocument document)
    {
        XmlElement xml = document.CreateElement(this.GetType().Name);
        // Create element, fill it with attributes declared with control
        return xml;
    }
}

public abstract class ContainerControl : Control
{
    public override XmlElement ToXML(XmlDocument document)
    {
        XmlElement xml = base.ToXML(document);
        // Use forech to fill XmlElement with child XmlElements
        return xml;
    }
}

public class Form : ContainerControl
{
    public override XmlElement ToXML(XmlDocument document)
    {
        XmlElement xml = base.ToXML(document);
        // Fill remaining elements declared in Form class
        return xml;
    }
}

但是我不确定如何用访客模式来做到这一点。这是基本的实现:

public class ToXmlVisitor : IVisitor
{
    public void Visit(Form form)
    {
    }
}

由于即使是抽象类也有助于实现,所以我不确定如何在ToXmlVisitor中正确执行此操作?

我考虑使用“访客”模式的原因是,某些算法将需要在实现类的项目中不可用的引用,并且存在许多不同的算法,因此我避免使用大型类。


你的问题是什么?
蚊蚋

基本上,如何使用访问者模式重写ToXml()方法。
Nezreli 2013年


感谢您的链接。动态分派简化了传统的访客模式,但变化不大。
Nezreli 2013年

@Nezreli是的。它适用于不支持“访客”模式的类,例如您正在处理的Windows Forms控件。
克里斯·范德莫顿

Answers:


17

访客模式是一种仅支持单绑定的编程语言中模拟双绑定的机制。不幸的是,该声明可能并不能使事情澄清很多,所以让我用一个简单的例子进行解释。

在.NET和C#(您正在使用的平台)中,可以使用ToString()函数将对象转换为字符串。该功能的作用,即正在执行的代码,取决于您将其应用到的对象的类型(这是一个虚拟方法)。执行什么代码取决于一件事,即对象的一种类型,因此使用的机制称为单一绑定。

但是,如果我想为每种不同类型的对象提供多种将对象转换为字符串的方法,该怎么办?如果我想有两种将对象转换为字符串的方式,以便执行的代码取决于两件事怎么办:不仅要转换的对象,而且还要我们转换对象的方式?

如果我们有双重绑定,可以很好地解决。但是大多数OO语言(包括C#)仅支持单一绑定。

访客模式通过将双重绑定转换为两个成功的单一绑定来解决该问题。

在上面的示例中,它将在对象中使用虚拟方法进行转换,这将在对象中调用实现转换算法的第二个虚拟方法。

但这意味着要在其上应用算法的对象需要与此协作:它需要支持访客模式。

您似乎正在使用.NET的Windows Forms类,该类不支持访问者模式。更具体地说,他们将需要一种public virtual void Accept(IVisitor)方法,显然他们没有。

那么,有什么选择呢?嗯,.NET不仅支持单绑定,还支持动态绑定,这比双绑定更穷。

有关如何应用该技术的更多信息,它将使您解决问题(如果我理解得很好),请查看“ 告别访客”

更新:

要将技术应用于您的特定问题,请首先定义您的扩展方法:

public static XmlDocument ToXml(this Control control)
{
    XmlDocument xml = new XmlDocument();
    XmlElement root = xml.CreateElement(control.GetType().Name);
    xml.AppendChild(root);

    Visit(control, xml, root);

    return xml;
}

创建动态调度程序:

private static void Visit(Control control, XmlDocument xml, XmlElement root)
{
    dynamic dynamicControl = control; //notice the 'dynamic' type.
                                      //this is the key to dynamic dispatch

    VisitCore(dynamicControl, xml, root);
}

然后填写具体方法:

private static void VisitCore(Control control, XmlDocument xml, XmlElement root)
{
    // TODO: specific Control handling
}

private static void VisitCore(ContainerControl control, XmlDocument xml, XmlElement root)
{
    // call the "base" method
    VisitCore(control as Control, xml, root);

    // TODO: specific ContainerControl handling
    // for example:
    foreach (Control child in control.Controls)
    {
        XmlElement element = xml.CreateElement(child.GetType().Name);
        root.AppendChild(element);

        // call the dynamic dispatcher method
        Visit(child, xml, element);
    }
}

private static void VisitCore(Form control, XmlDocument xml, XmlElement root)
{
    // call the "base" method
    VisitCore(control as ContainerControl, xml, root);

    // TODO: specific Form handling
}

.NET中的动态调度确实非常强大..但是我注意到它可能会有点...很好..速度很慢,但是它在一行代码中确实需要多个类中的行以及与访问者的接口
Newtopian

尽管如此,动态调度仍无法解决我的问题,因为我的ToXml算法要求我“访问”继承链中的所有类型。在我的示例中,为了成功进行XML转换,它需要访问Control,ContainterControl和Form。
Nezreli

@Nezreli它可以解决您的问题,我已经更新了答案,向您展示了如何。
克里斯·范德莫顿

我同意在动态变量定义中添加注释。在发现之前,我花了两遍代码,这是整个故事的关键。
Cristi Diaconescu 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.