Zrozumienie wzoru odwiedzin

16

Mam hierarchię klas reprezentujących kontrolki GUI. Coś takiego:

Control->ContainerControl->Form

Muszę zaimplementować serię algorytmów, które działają z obiektami wykonującymi różne czynności i myślę, że wzorzec dla gości byłby najczystszym rozwiązaniem. Weźmy na przykład algorytm, który tworzy reprezentację Xml hierarchii obiektów. Stosując podejście „klasyczne” zrobiłbym to:

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

Ale nie jestem pewien, jak to zrobić ze wzorcem odwiedzin. To jest podstawowa implementacja:

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

Ponieważ nawet klasy abstrakcyjne pomagają w implementacji, nie jestem pewien, jak zrobić to poprawnie w ToXmlVisitor?

Powodem, dla którego rozważam wzorzec użytkownika, jest to, że niektóre algorytmy będą wymagały odwołań niedostępnych w projekcie, w którym zaimplementowane są klasy, i istnieje wiele różnych algorytmów, więc unikam dużych klas.

Nezreli
źródło
jakie jest Twoje pytanie?
komar
Zasadniczo, jak przepisać metodę ToXml () przy użyciu wzorca gościa.
Nezreli
Dzięki za link. Dynamiczna wysyłka upraszcza tradycyjny wzór odwiedzin, ale niewiele się zmienia.
Nezreli
@Nezreli Tak to robi. Działa z klasami, które nie obsługują wzorca gościa, takimi jak formanty Windows Forms, z którymi masz do czynienia.
Kris Vandermotten,

Odpowiedzi:

17

Wzorzec gościa jest mechanizmem symulującym podwójne wiązanie w językach programowania, które obsługują tylko pojedyncze wiązanie. Niestety, to stwierdzenie może nie wyjaśniać wielu rzeczy, dlatego wyjaśnię to prostym przykładem.

W .NET i C #, używanej platformie, obiekty można konwertować na ciągi znaków za pomocą ToString()funkcji. To, co robi ta funkcja, tj. Wykonywany kod, zależy od typu obiektu, do którego się go stosuje (jest to metoda wirtualna). To, jaki kod zostanie wykonany, zależy od jednej rzeczy, jednego typu obiektu, dlatego zastosowany mechanizm nazywa się pojedynczym wiązaniem.

Ale co jeśli chcę mieć więcej niż jeden sposób przekonwertowania obiektu na ciąg, dla każdego innego rodzaju obiektu? Co jeśli chciałbym mieć dwa sposoby konwertowania obiektów na ciągi, tak że wykonywany kod zależy od dwóch rzeczy: nie tylko obiektu do konwersji, ale także sposobu, w jaki chcemy go przekonwertować?

Można to ładnie rozwiązać, gdybyśmy mieli podwójne wiązanie. Ale większość języków OO, w tym C #, obsługuje tylko pojedyncze wiązanie.

Wzór odwiedzającego rozwiązuje problem, zamieniając podwójne wiązanie w dwa kolejne pojedyncze wiązania.

W powyższym przykładzie użyłby do konwersji metody wirtualnej w obiekcie, która wywołuje drugą metodę wirtualną w obiekcie implementującym algorytm konwersji.

Ale to oznacza, że ​​obiekt, na którym chcesz zastosować algorytm, musi z tym współpracować: musi mieć wsparcie dla wypalonego wzorca odwiedzającego.

Wygląda na to, że używasz klas Windows Forms .NET, które nie obsługują wzorca odwiedzającego. Mówiąc dokładniej, musieliby mieć public virtual void Accept(IVisitor)metodę, której oczywiście nie mają.

Więc jaka jest alternatywa? Cóż. .NET obsługuje nie tylko pojedyncze wiązanie, ale także dynamiczne wiązanie, które jest jeszcze bardziej wydajne niż podwójne wiązanie.

Aby uzyskać więcej informacji na temat zastosowania tej techniki, która pozwoli ci rozwiązać problem (jeśli dobrze go rozumiem), spójrz na Farewell Visitor .

AKTUALIZACJA:

Aby zastosować technikę do konkretnego problemu, najpierw zdefiniuj metodę rozszerzenia:

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

Utwórz dynamicznego dyspozytora:

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

Następnie wprowadź określone metody:

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
źródło
dynamiczna wysyłka w .NET jest rzeczywiście dość potężna .. zauważyłem jednak, że może być trochę ... no cóż ... powolny, ale robi to w jednym wierszu kodu, co łączy linie sevral w wielu klasach i interfejsy z gościem
Newtopian
Mimo to dynamiczna wysyłka nie rozwiąże mojego problemu, ponieważ mój algorytm ToXml wymaga ode mnie „odwiedzania” wszystkich typów w łańcuchu dziedziczenia. W moim przykładzie musi on odwiedzić Control, ContainterControl i Form w tej kolejności, aby uzyskać udaną konwersję XML.
Nezreli
@Nezreli Może rozwiązać Twój problem, zaktualizowałem swoją odpowiedź, aby pokazać Ci, jak to zrobić.
Kris Vandermotten
Zezwoliłem na dodanie komentarza do definicji zmiennej dynamicznej. Dwa razy przeczytałem kod, zanim go zauważyłem, i jest to klucz do całej historii.
Cristi Diaconescu