Comprender el patrón de visitante

16

Tengo una jerarquía de clases que representa los controles de la GUI. Algo como esto:

Control->ContainerControl->Form

Tengo que implementar una serie de algoritmos que funcionan con objetos que hacen varias cosas y estoy pensando que el patrón de visitante sería la solución más limpia. Tomemos, por ejemplo, un algoritmo que crea una representación Xml de una jerarquía de objetos. Usando el enfoque 'clásico', haría esto:

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;
    }
}

Pero no estoy seguro de cómo hacer esto con el patrón de visitante. Esta es la implementación básica:

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

Dado que incluso las clases abstractas ayudan con la implementación, no estoy seguro de cómo hacerlo correctamente en ToXmlVisitor.

La razón por la que estoy considerando el patrón de visitante es que algunos algoritmos necesitarán referencias no disponibles en el proyecto donde se implementan las clases y hay una serie de algoritmos diferentes, por lo que estoy evitando clases grandes.

Nezreli
fuente
¿Cuál es tu pregunta?
mosquito
Básicamente, cómo reescribir el método ToXml () utilizando un patrón de visitante.
Nezreli
Echa un vistazo a blogs.u2u.net/kris/post/2010/11/30/Farewell-Visitor.aspx
Kris Vandermotten
Gracias por el enlace. El despacho dinámico simplifica el patrón tradicional de visitantes pero no cambia mucho.
Nezreli
@Nezreli Sí, lo hace. Funciona con clases que no admiten el patrón de visitante, como los controles de formularios Windows Forms con los que está trabajando.
Kris Vandermotten

Respuestas:

17

El patrón de visitante es un mecanismo para simular doble enlace en lenguajes de programación que solo admiten enlace único. Desafortunadamente, esa declaración podría no aclarar mucho las cosas, así que permítanme explicarlo con un ejemplo simple.

En .NET y C #, la plataforma que está utilizando, los objetos se pueden convertir en cadenas mediante la ToString()función. Lo que hace esa función, es decir, el código que se ejecuta, depende del tipo de objeto al que lo esté aplicando (es un método virtual). El código que se ejecuta depende de una cosa, el tipo de objeto, por lo tanto, el mecanismo utilizado se llama enlace único.

Pero, ¿qué pasa si quiero tener más de una forma de convertir un objeto en una cadena, para cada tipo diferente de objeto? ¿Qué pasaría si quisiera tener dos formas de convertir objetos en cadenas, de modo que el código que se ejecuta dependa de dos cosas: no solo el objeto a convertir, sino también la forma en que queremos que se convierta?

Eso podría resolverse bien si tuviéramos doble enlace. Pero la mayoría de los lenguajes OO, incluido C #, solo admiten el enlace único.

El patrón de visitante resuelve el problema al convertir el enlace doble en dos enlaces únicos sucesivos.

En nuestro ejemplo anterior, usaría un método virtual en el objeto para convertir, que llama a un segundo método virtual en el objeto que implementa el algoritmo de conversión.

Pero eso implica que el objeto sobre el que desea aplicar el algoritmo debe colaborar con esto: debe tener soporte para el patrón de visitante integrado.

Parece que está utilizando las clases de formularios Windows Forms de .NET, que no tienen soporte para el patrón de visitante. Más específicamente, tendrían que tener un public virtual void Accept(IVisitor)método, que obviamente no tienen.

Entonces, ¿cuál es la alternativa? Bueno, .NET no solo admite el enlace único, sino que también es compatible con el enlace dinámico, que es aún más potente que el enlace doble.

Para obtener más información sobre cómo aplicar esa técnica, que le permitirá resolver su problema (si lo entiendo bien), eche un vistazo a Farewell Visitor .

ACTUALIZAR:

Para aplicar la técnica a su problema específico, primero defina su método de extensión:

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;
}

Cree el despachador dinámico:

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);
}

Luego complete los métodos específicos:

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
}
Kris Vandermotten
fuente
el despacho dinámico en .NET es bastante poderoso ... sin embargo, noté que podría ser un poco ... bueno ... lento, pero lo hace en una sola línea de código que toma varias líneas en múltiples clases e interfaces con un visitante
Newtopian
Aún así, el despacho dinámico no resolverá mi problema porque mi algoritmo ToXml requiere que 'visite' todos los tipos de la cadena de herencia. En mi ejemplo, debe visitar Control, ContainterControl y Form en ese orden para tener una conversión XML exitosa.
Nezreli
@Nezreli Puede resolver su problema, he actualizado mi respuesta para mostrarle cómo.
Kris Vandermotten
Tomé el permiso para agregar un comentario a la definición de variable dinámica. Me tomó dos lecturas del código antes de detectarlo, y es la clave de toda la historia.
Cristi Diaconescu