web-dev-qa-db-ja.com

訪問者パターンを理解する

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でそれを適切に行う方法がわかりませんか?

ビジターパターンを検討している理由は、一部のアルゴリズムでは、クラスが実装されているプロジェクトで利用できない参照が必要であり、多数の異なるアルゴリズムがあるため、大きなクラスを避けているためです。

16
Nezreli

訪問者パターンは、単一バインディングのみをサポートするプログラミング言語で二重バインディングをシミュレートするメカニズムです。残念ながら、そのステートメントでは説明があまり明確にならない可能性があるため、簡単な例で説明します。

.NETおよびC#では、使用しているプラ​​ットフォームで、ToString()関数を使用してオブジェクトを文字列に変換できます。その機能、つまり実行されるコードは、適用先のオブジェクトのタイプによって異なります(仮想メソッドです)。どのコードが実行されるかは、オブジェクトの1つのタイプに依存するため、使用されるメカニズムはシングルバインディングと呼ばれます。

しかし、オブジェクトの種類ごとに、オブジェクトを文字列に変換する複数の方法が必要な場合はどうなりますか?オブジェクトを文字列に変換する2つの方法を使用して、実行されるコードが2つの要素に依存するようにしたい場合はどうなりますか:変換するオブジェクトだけでなく、変換する方法も異なります。

二重バインディングがあれば、うまく解決できます。しかし、ほとんどのOO C#を含む言語は、単一バインディングのみをサポートしています。

ビジターパターンは、デュアルバインディングを2つの連続したシングルバインディングに変えることで問題を解決します。

上記の例では、変換するオブジェクトの仮想メソッドを使用して、変換アルゴリズムを実装するオブジェクトの2番目の仮想メソッドを呼び出します。

しかし、これは、アルゴリズムを適用するオブジェクトがこれと連携する必要があることを意味します。組み込まれたビジターパターンをサポートする必要があります。

ビジターパターンをサポートしていない.NETのWindowsフォームクラスを使用しているようです。具体的には、public virtual void Accept(IVisitor)メソッドが必要になりますが、これは明らかにありません。

それで、代替は何ですか? .NETは、シングルバインディングをサポートするだけでなく、デュアルバインディングよりも強力な動的バインディングもサポートします。

あなたが問題を解決できるようにするそのテクニックを適用する方法の詳細については(私がそれをよく理解していれば)、 Farewell Visitor を見てください。

UPDATE:

この手法を特定の問題に適用するには、まず拡張メソッドを定義します。

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