Javaの訪問者デザインパターン
1. 概要
このチュートリアルでは、動作GoFデザインパターンの1つであるVisitorを紹介します。
まず、その目的と解決しようとしている問題について説明します。
次に、VisitorのUML図と実際の例の実装を見ていきます。
2. 訪問者のデザインパターン
Visitorパターンの目的は、既存のオブジェクト構造に変更を加えずに新しい操作を定義することです。
コンポーネントで構成されるlink:/ java-composite-pattern objectがあると想像してください。 オブジェクトの構造は固定されています。オブジェクトを変更できないか、新しいタイプの要素を構造に追加する予定はありません。
さて、既存のクラスを変更せずに、どのようにしてコードに新しい機能を追加できますか?
訪問者のデザインパターンが答えかもしれません。 簡単に言えば、we’ll have to do is to add a function which accepts the visitor class to each element of the structure.
このようにして、コンポーネントはビジターの実装がそれらを「訪問」し、その要素に対して必要なアクションを実行できるようにします。
つまり、クラスからオブジェクト構造に適用されるアルゴリズムを抽出します。
したがって、we’ll make good use of the Open/Closed principle はコードを変更しないためですが、新しいVisitor 実装を提供することで、機能を拡張することができます。
3. UML図
上記のUMLダイアグラムには、2つの実装階層、専門の訪問者、および具体的な要素があります。
まず、クライアントはVisitor実装を使用し、それをオブジェクト構造に適用します。 複合オブジェクトはそのコンポーネントを反復処理し、ビジターを各コンポーネントに適用します。
さて、特に関連するのは、concrete elements (ConcreteElementA and ConcreteElementB) are accepting a Visitor, simply allowing it to visit them.
最後に、このメソッドは構造内のすべての要素で同じであり、(thisキーワードを介して)訪問者のvisitメソッドに自分自身を渡してdouble dispatchを実行します。
4. 実装
この例は、JSONとXMLの具象要素で構成されるカスタムDocument objectです。要素には、共通の抽象スーパークラス、Element.があります
Documentクラス:
public class Document extends Element {
List elements = new ArrayList<>();
// ...
@Override
public void accept(Visitor v) {
for (Element e : this.elements) {
e.accept(v);
}
}
}
Elementクラスには、Visitor interfaceを受け入れる抽象メソッドがあります。
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. テスト
テストの目的で、VisitorDemoclassを見てみましょう。
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);
}
// ...
}
まず、ElementVisitorを作成します。これは、要素に適用するアルゴリズムを保持します。
次に、適切なコンポーネントを使用してDocument を設定し、オブジェクト構造のすべての要素によって受け入れられるビジターを適用します。
出力は次のようになります。
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
これは、訪問者がElement typeに応じて構造の各要素にアクセスしたことを示しており、適切なメソッドに処理をディスパッチし、基になるすべてのオブジェクトからデータを取得できます。
6. 欠点
各デザインパターンとして、ビジターでさえ欠点があります。特に、使用率 makes it more difficult to maintain the code if we need to add new elements to the object’s structure.
たとえば、新しいYamlElement,を追加する場合、この要素の処理に必要な新しいメソッドで既存のすべての訪問者を更新する必要があります。 さらにこれに続いて、10人以上の具体的な訪問者がいる場合、すべての訪問者を更新するのは面倒かもしれません。
これ以外に、このパターンを使用すると、1つの特定のオブジェクトに関連するビジネスロジックがすべての訪問者実装に広がります。
7. 結論
Visitorパターンは、アルゴリズムを操作対象のクラスから分離するのに最適です。 それに加えて、Visitorの新しい実装を提供するだけで、新しい操作をより簡単に追加できます。
さらに、コンポーネントインターフェースに依存していません。コンポーネントのインターフェースが異なっていても、具体的な要素ごとに処理するための個別のアルゴリズムがあるため、問題ありません。
さらに、Visitorは最終的に、横断する要素に基づいてデータを集約できます。
ビジターデザインパターンのより専門的なバージョンを確認するには、visitor pattern in Java NIO(JDKでのパターンの使用法)を確認してください。
いつものように、完全なコードはGithub projectで入手できます。