Noções básicas sobre o padrão de visitantes

16

Eu tenho uma hierarquia de classes que representa controles da GUI. Algo assim:

Control->ContainerControl->Form

Eu tenho que implementar uma série de algoritmos que funcionam com objetos fazendo várias coisas e estou pensando que o padrão Visitor seria a solução mais limpa. Tomemos, por exemplo, um algoritmo que cria uma representação XML de uma hierarquia de objetos. Usando a abordagem 'clássica', eu faria o seguinte:

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

Mas não sei como fazer isso com o padrão de visitantes. Esta é a implementação básica:

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

Como até as classes abstratas ajudam na implementação, não sei como fazer isso corretamente no ToXmlVisitor?

A razão pela qual estou considerando o padrão Visitor é que alguns algoritmos precisarão de referências não disponíveis no projeto em que as classes são implementadas e há vários algoritmos diferentes, por isso estou evitando grandes classes.

Nezreli
fonte
qual a sua pergunta
mosquito
Basicamente, como reescrever o método ToXml () usando um padrão de visitante.
Nezreli # 23/13
Dê uma olhada no blogs.u2u.net/kris/post/2010/11/30/Farewell-Visitor.aspx
Kris Vandermotten
Obrigado pelo link. O envio dinâmico simplifica o padrão tradicional de visitantes, mas não muda muito.
Nezreli 23/10/2013
@Nezreli Sim, sim. Ele funciona com classes que não oferecem suporte ao padrão Visitor, como os controles do Windows Forms com os quais você está lidando.
Kris Vandermotten

Respostas:

17

O padrão de visitante é um mecanismo para simular a ligação dupla em linguagens de programação que suportam apenas a ligação única. Infelizmente, essa afirmação pode não esclarecer muito as coisas, então deixe-me explicar com um exemplo simples.

No .NET e C #, a plataforma que você está usando, os objetos podem ser convertidos em strings usando a ToString()função O que essa função faz, ou seja, o código que está sendo executado, depende do tipo de objeto ao qual você está aplicando (é um método virtual). O código executado depende de uma coisa, o único tipo do objeto; portanto, o mecanismo usado é chamado de ligação única.

Mas e se eu quiser ter mais de uma maneira de converter um objeto em uma string, para cada tipo diferente de objeto? E se eu quisesse ter duas maneiras de converter objetos em seqüências de caracteres, para que o código que está sendo executado dependa de duas coisas: não apenas o objeto a ser convertido, mas também a maneira pela qual queremos que ele seja convertido?

Isso poderia ser resolvido bem se tivéssemos dupla ligação. Mas a maioria dos idiomas OO, incluindo C #, suporta apenas ligação única.

O padrão de visitante resolve o problema, transformando a ligação dupla em duas ligações simples sucessivas.

No nosso exemplo acima, ele usaria um método virtual no objeto para converter, que chama um segundo método virtual no objeto que implementa o algoritmo de conversão.

Mas isso implica que o objeto no qual você deseja aplicar o algoritmo precisa colaborar com isso: ele precisa ter suporte para o padrão de visitante inserido.

Você parece estar usando as classes Windows Forms do .NET, que não têm suporte para o padrão de visitantes. Mais especificamente, eles precisariam ter um public virtual void Accept(IVisitor)método, o que obviamente eles não têm.

Então, qual é a alternativa? Bem, o .NET não suporta apenas a ligação única, mas também a ligação dinâmica, que é ainda mais eficiente que a ligação dupla.

Para obter mais informações sobre como aplicar essa técnica, que permitirá que você resolva seu problema (se bem entendi), dê uma olhada no Farewell Visitor .

ATUALIZAR:

Para aplicar a técnica ao seu problema específico, primeiro defina seu método de extensão:

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

Crie o expedidor 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);
}

Em seguida, preencha os 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
fonte
o envio dinâmico no .NET é realmente muito poderoso .. no entanto, notei que poderia ser um pouco ... bem ... lento, mas faz em uma única linha de código o que leva linhas sevral em várias classes e faz interface com um visitante
Newtopian
Ainda assim, o envio dinâmico não resolverá meu problema porque meu algoritmo ToXml exige que eu 'visite' todos os tipos da cadeia de herança. No meu exemplo, ele precisa visitar Control, ContainterControl e Form nessa ordem para obter uma conversão XML bem-sucedida.
Nezreli #
@ Nezreli Pode resolver seu problema, atualizei minha resposta para mostrar como.
Kris Vandermotten
Tomei a permissão para adicionar um comentário à definição de variável dinâmica. Levei duas leituras do código antes de identificá-lo, e é a chave para toda a história.
Cristi Diaconescu