1

多くの欠点がなく、ビジター機能をエミュレートするためのクリーンなデザインを探しています。Java では、従来の実装 (GoF で説明されているように) は、if-else を取り除くために二重ディスパッチに頼っています。これを解決するために、リフレクションを使用して「Visitable」クラスの変更を回避する実装を見てきましたが、これらはメソッド名を探すときにハードコードされた文字列に依存しています。非常に便利ですが、それでもきれいなデザインではないと思います。

データ構造や優れた OO 設計を使用して同じアイデアをエミュレートすることは可能ですか? パターンである必要はありません。同様の問題が解決される例を探しているだけです (例: a を使用Map<Class<T>,SomeFunctionObject>)。


更新次のようなもの:

    public abstract class BaseVisitor<T> {

        private final TypesafeHeterogeneusMap map;

        protected BaseVisitor(){
            map = inflateFunctions();
        }   

        public <E extends T> void  process(E element){
            if(element == null){
                throw new NullPointerException();
            }
            boolean processed = false;

            @SuppressWarnings("unchecked")
            Class<? super T> sc = (Class<? super T>) element.getClass();

            while(true){            
                if(sc != null){
                    FunctionObject<? super T> fo2 = map.get(sc);
                    if(fo2 != null){
                        fo2.process(element);
                        processed = true;
                        break;
                    }
                    sc = sc.getSuperclass();
                } else {
                    break;
                }
            }

            if(!processed) System.out.println("Unknown type: " + element.getClass().getName());     
        }

        abstract TypesafeHeterogeneusMap inflateFunctions();
    }

実際には、テンプレート パターンとコマンド パターンが混在していると思います。それを強化する方法についての提案を自由に投稿してください。

4

3 に答える 3

3

すべての Visitor 実装を基本クラスに拡張するだけで、すべてのタイプの Visitable にデフォルトの実装を提供できます。

public interface AnimalVisitor {
    void visitHorse(Horse horse);
    void visitDog(Dog dog);
}

public class BaseAnimalVisitor implements AnimalVisitor {
    public void visitHorse(Horse horse) {
        // do nothing by default
    }
    public void visitDog(Dog dog) {
        // do nothing by default
    }
}

次に、新しいクラスCatが導入されたら、visitCat(Cat cat)メソッドをインターフェイスと基本クラスに追加します。すべてのビジターは変更されずにコンパイルされます。猫を無視したくない場合は、visitCatメソッドをオーバーライドします。

于 2012-01-12T14:36:29.237 に答える
2

あなたが探している答えではありませんが、Java よりも高レベルで冗長性の低い言語を使用することを検討してください。Visitor パターンのようなものが無関係に見え始めることに気付くでしょう。もちろん、ある場所でデータ構造をトラバースするためのロジックを定義し、別の場所で (型に基づいて) データ構造の要素をどうするかを定義し、ミックスアンドマッチのトラバーサルを可能にする場合は、 /処理戦略、あなたはそれを行うことができます。しかし、「パターン」と呼ぶようなものは何もない、ほんの少量の簡単なコードを使用してそれを行うことができます。

私は C/Java プログラミングのバックグラウンドを持ち、数年前にさまざまな動的言語を学び始めました。数行のコードでどれだけのことができるかを理解するのは驚くべきことでした。

たとえば、Ruby で Visitor パターンをエミュレートする場合:

module Enumerable
  def accept_visitor(visitor)
    each do |elem|
      method = "visit#{elem.class}".to_sym
      elem.send(method,elem) if elem.respond_to? method
    end
  end
end

説明すると、Ruby では、Enumerable は繰り返し処理できるものすべてを表します。これらの 8 行のコードで、繰り返し処理できるあらゆる種類のオブジェクトが Visitor を受け入れるようにしました。5 つ、10 つ、または 100 の異なるクラスでビジターを受け入れようとしても、必要なのはこれらの 8 行だけです。

ビジターの例を次に示します。

class CatCounter
  attr_reader :count
  def initialize; @count  = 0; end
  def visitCat;   @count += 1; end
end

Visitor は、すべての異なるタイプの Visitableに対してメソッドを定義する必要はないことに注意してください。各 Visitor は、関心のある Visitable のタイプのメソッドを定義するだけです。残りは無視できます。(つまり、新しいタイプの Visitable を追加する場合、既存のコードの束を変更する必要はありません。) また、すべてのVisitor は、Visitors を受け入れる任意のオブジェクトと相互運用できます。

この数行のコードだけで、あなたが言及したビジター パターンの問題はすべて解決されています。

誤解しないでください。Java は、いくつかの点で優れた言語です。ただし、作業に適したツールを選択する必要があります。ツールの制限を克服するために多大な努力をしているという事実は、この場合、別のツールが必要であることを示している可能性があります。

于 2012-01-23T19:45:35.013 に答える
0

@MisterSmith、Javaを使用する必要があり、おそらくVisitorを使用する正当な理由があるため、別の可能な解決策を提案します。

Visitor の通常の実装方法から離れて、そもそも人々が Visitor を使用する理由に戻りましょう。他の回答でも述べましたが、Visitor のポイントは、トラバーサルと処理ロジックを組み合わせて使用​​できるようにすることです。

「トラバーサル ロジック」とは、さまざまな種類のデータ構造をトラバースするためのロジック、または同じデータ構造を異なる順序でトラバースするためのロジックを意味します。または、返された要素に特定のフィルターを適用するトラバーサル戦略などを含めることもできます。

Visitor では暗黙的に、各要素に適用する処理がそのクラスに依存するという考えがあります。各要素に対する処理がそのクラスに依存しない場合、Visitor を使用する理由はありません。要素クラスで「切り替え」を行いたい場合を除き、これを行うには仮想メソッド呼び出しを使用する必要があります (これが、通常の Java 実装が二重ディスパッチを使用する理由です)。

Visitor パターンを2 つではなく3 つに分割することを提案します。

  1. 特定のトラバーサルを実装する Iterator オブジェクト

  2. 「クラスに基づいて要素をどうするかを決定する」戦略を実装するオブジェクト (通常は二重ディスパッチが必要な部分)。リフレクションを使用して、これを行う汎用クラスを作成できます。単純な実装では Map を使用するか、バイトコードを動的に生成するものを作成できます (生のバイトコードを新しいクラスとしてロードできる Java のプラットフォーム メソッドは忘れましたが、1 つ存在します)。また!または、JRuby や Clojure などの動的な JVM ホスト言語を使用して #2 を記述し、バイトコードにコンパイルして、結果のファイルを使用することも.classできます。(このファイルはおそらくinvokedynamic、私が知る限り、Java からアクセスできないバイトコードを使用します。Java コンパイラはそれを出力しません。これが変更された場合は、この投稿を編集してください。)

  3. 訪問者自身。この実装では、訪問者は共通のスーパークラスからサブクラス化する必要も、関心のない要素のメソッドを実装する必要もありません。

トラバーサルを汎用イテレーターに保持すると、それを使用して他のことを行うことができます (ビジターを受け入れるだけではありません)。

3 つのピースを結合するにはいくつかの方法があります。#2 は #3 をラップすると考えています (コンストラクターの引数として使用します)。#2 は、Iterator を引数として受け取り、それに Visitor を適用する public メソッドを提供します。

興味深い部分は#2です。後でこの投稿を編集して、サンプル実装を追加するかもしれません。今、私は他にやるべきことがいくつかあります。他の誰かが実装を思いついた場合は、ここに追加してください。

于 2012-01-24T10:04:22.193 に答える