1. 概要

このチュートリアルでは、動作GoFデザインパターンの1つであるVisitorを紹介します。

まず、その目的と解決しようとしている問題について説明します。

次に、VisitorのUML図と実際の例の実装を見ていきます。

2. ビジターデザインパターン

ビジターパターンの目的は、既存のオブジェクト構造に変更を加えることなく、新しい操作を定義することです。

コンポーネントで構成されるコンポジットオブジェクトがあるとします。 オブジェクトの構造は固定されています。オブジェクトを変更できないか、新しいタイプの要素を構造に追加する予定はありません。

では、既存のクラスを変更せずに、コードに新しい機能を追加するにはどうすればよいでしょうか。

ビジターのデザインパターンが答えかもしれません。 簡単に言えば、構造の各要素にビジタークラスを受け入れる関数を追加する必要があります。

そうすることで、コンポーネントを使用して、訪問者の実装がそれらを「訪問」し、その要素に対して必要なアクションを実行できるようになります。

つまり、オブジェクト構造に適用されるアルゴリズムをクラスから抽出します。

したがって、コードを変更しないため、のオープン/クローズの原則をうまく利用しますが、新しいビジターを提供することで機能を拡張できます。 ]実装。

3. UML図

上のUMLダイアグラムには、2つの実装階層、特殊な訪問者、および具体的な要素があります。

まず、クライアントはVisitor実装を使用して、それをオブジェクト構造に適用します。 複合オブジェクトはそのコンポーネントを反復処理し、訪問者を各コンポーネントに適用します。

ここで、特に関連するのは、コンクリート要素(ConcreteElementAおよびConcreteElementB)が訪問者を受け入れ、単にそれらを訪問できるようにすることです。

最後に、このメソッドは構造内のすべての要素で同じであり、ダブルディスパッチを実行し、それ自体を( this キーワードを介して)訪問者のvisitメソッドに渡します。

4. 実装

この例は、JSONおよびXML具象要素で構成されるカスタムドキュメントオブジェクトです。 要素には、共通の抽象スーパークラスElement。があります。

Document クラス:

public class Document extends Element {

    List<Element> elements = new ArrayList<>();

    // ...

    @Override
    public void accept(Visitor v) {
        for (Element e : this.elements) {
            e.accept(v);
        }
    }
}

Element クラスには、Visitorインターフェイスを受け入れる抽象メソッドがあります。

public abstract void accept(Visitor v);

したがって、新しい要素を作成するときに、 JsonElement という名前を付けて、このメソッドの実装を提供する必要があります。

ただし、Visitorパターンの性質上、実装は同じであるため、ほとんどの場合、他の既存の要素からボイラープレートコードをコピーして貼り付ける必要があります。

public class JsonElement extends Element {

    // ...

    public void accept(Visitor v) {
        v.visit(this);
    }
}

私たちの要素はすべての訪問者がそれらを訪問できるので、 Document 要素を処理したいとしますが、クラスタイプに応じてそれぞれ異なる方法で処理します。

したがって、訪問者は特定のタイプに対して別の方法を使用します。

public class ElementVisitor implements Visitor {

    @Override
    public void visit(XmlElement xe) {
        System.out.println(
          "processing an XML element with uuid: " + xe.uuid);
    }

    @Override
    public void visit(JsonElement je) {
        System.out.println(
          "processing a JSON element with uuid: " + je.uuid);
    }
}

ここで、具体的な訪問者は2つのメソッドを実装します。それぞれ、Elementのタイプごとに1つです。

これにより、必要なアクションを実行できる構造の特定のオブジェクトにアクセスできます。

5. テスト

テストの目的で、VisitorDemoクラスを見てみましょう。

public class VisitorDemo {

    public static void main(String[] args) {

        Visitor v = new ElementVisitor();

        Document d = new Document(generateUuid());
        d.elements.add(new JsonElement(generateUuid()));
        d.elements.add(new JsonElement(generateUuid()));
        d.elements.add(new XmlElement(generateUuid()));

        d.accept(v);
    }

    // ...
}

まず、 Element Visitorを作成します。これは、要素に適用するアルゴリズムを保持します。

次に、適切なコンポーネントを使用してドキュメントを設定し、オブジェクト構造のすべての要素によって受け入れられるビジターを適用します。

出力は次のようになります。

processing a JSON element with uuid: fdbc75d0-5067-49df-9567-239f38f01b04
processing a JSON element with uuid: 81e6c856-ddaf-43d5-aec5-8ef977d3745e
processing an XML element with uuid: 091bfcb8-2c68-491a-9308-4ada2687e203

これは、訪問者が構造の各要素を訪問したことを示しています。要素タイプに応じて、適切なメソッドに処理をディスパッチし、基礎となるすべてのオブジェクトからデータを取得できます。

6. 欠点

それぞれのデザインパターンとして、Visitorでさえ欠点があります。特に、その使用法により、オブジェクトの構造に新しい要素を追加する必要がある場合、コードの保守がより困難になります。

たとえば、新しい YamlElement、を追加する場合、この要素を処理するために必要な新しいメソッドですべての既存の訪問者を更新する必要があります。 さらに、具体的な訪問者が10人以上いる場合は、すべてを更新するのは面倒かもしれません。

これ以外に、このパターンを使用すると、特定の1つのオブジェクトに関連するビジネスロジックがすべての訪問者の実装に分散されます。

7. 結論

ビジターパターンは、アルゴリズムを操作するクラスから分離するのに最適です。 さらに、Visitorの新しい実装を提供するだけで、新しい操作をより簡単に追加できます。

さらに、コンポーネントインターフェイスに依存することはありません。コンポーネントのインターフェイスが異なる場合は、具体的な要素ごとに処理するための個別のアルゴリズムがあるため、問題ありません。

さらに、訪問者は最終的に、通過する要素に基づいてデータを集約できます。

ビジターデザインパターンのより特殊なバージョンを確認するには、Java NIOビジターパターン–JDKでのパターンの使用法を確認してください。

いつものように、完全なコードはGithubプロジェクトで入手できます。