Понимание структуры посетителя

16

У меня есть иерархия классов, которая представляет элементы управления GUI. Что-то вроде этого:

Control->ContainerControl->Form

Я должен реализовать ряд алгоритмов, которые работают с объектами, делающими разные вещи, и я думаю, что шаблон Visitor будет самым чистым решением. Давайте возьмем, например, алгоритм, который создает представление 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?

Причина, по которой я рассматриваю шаблон Visitor, заключается в том, что некоторым алгоритмам потребуются ссылки, недоступные в проекте, где реализованы классы, и существует ряд различных алгоритмов, поэтому я избегаю больших классов.

Nezreli
источник
какой у Вас вопрос?
комнат
Как переписать метод ToXml (), используя шаблон посетителя.
Незрели
Взгляните на blogs.u2u.net/kris/post/2010/11/30/Farewell-Visitor.aspx
Крис Вандермоттен,
Спасибо за ссылку. Динамическая отправка упрощает традиционный шаблон посетителей, но не сильно меняет.
Незрели
@ Nezreli Да, это так. Он работает с классами, которые не поддерживают шаблон Visitor, например с элементами управления Windows Forms, с которыми вы имеете дело.
Крис Вандермоттен,

Ответы:

17

Шаблон посетителя - это механизм для симуляции двойного связывания в языках программирования, которые поддерживают только одиночное связывание. К сожалению, это утверждение не может многое прояснить, поэтому позвольте мне объяснить на простом примере.

В используемой платформе .NET и C # объекты могут быть преобразованы в строки с помощью ToString() функции. То, что делает эта функция, т.е. выполняемый код, зависит от типа объекта, к которому вы применяете ее (это виртуальный метод). То, какой код выполняется, зависит от одной вещи, одного типа объекта, поэтому используемый механизм называется одиночным связыванием.

Но что, если я хочу иметь более одного способа преобразования объекта в строку для каждого объекта другого типа? Что если бы я хотел иметь два способа преобразования объектов в строки, чтобы выполняемый код зависел от двух вещей: не только от объекта, который нужно преобразовать, но и от способа, которым мы хотим, чтобы он был преобразован?

Это можно было бы решить, если бы у нас было двойное связывание. Но большинство ОО-языков, включая 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 Это может решить вашу проблему, я обновил свой ответ, чтобы показать вам, как.
Крис Вандермоттен,
Я получил разрешение добавить комментарий к определению динамической переменной. Мне потребовалось два чтения кода, прежде чем я его заметил, и это ключ ко всей истории.
Кристи Диаконеску