6

ビジターデザインパターンをコードに適用することと、次のアプローチとの違いは何ですか。

interface Dointerface {
    public void perform(Object o);
}

public class T {
    private Dointerface d;
    private String s;

    public String getS() {
            return s;
    }

    public T(String s) {
            this.s = s;
    }

    public void setInterface(Dointerface d) {
            this.d = d;
    }

    public void perform() {
            d.perform(this);
    }

    public static void main(String[] args) {
            T t = new T("Geonline");
            t.setInterface(new Dointerface() {
                    public void perform(Object o) {
                            T a = (T)o;
                            System.out.println(a.getS());
                    }
            });
            t.perform();
    }
}

インターフェイスを使用することで、アルゴリズムを実際に分離しているわけではないと思います。

4

5 に答える 5

8

かなり大きな違いがあります。

ビジター パターンはインターフェイスを使用しますが、その目的は、クラスを変更せずに 1 つ以上のクラス (インターフェイスを実装するクラス) に対して操作を実行できるようにすることです。したがって、実装は実際にクラスを「訪問」し、クラスを変更せずに処理を行います。

インターフェースは、潜在的に多様なクラスのグループに共通の API を提供するために使用される基本的な概念です。インターフェイスの典型的なテストは、それを共有するクラスが少なくとも 1 つの点で似ている (is-like-a) ことであり、それらの場合はそのように扱うことができます。

これは、Java での 2 人の訪問者を示すウィキペディアの簡単な例です。

于 2008-10-11T18:49:46.003 に答える
4

ビジターパターンは、多くの異なるクラスで構成されるデータ構造があり、クラスごとに異なる操作を必要とする複数のアルゴリズムがある場合に使用されます。この例では、DoInterface実装は1つのタイプに対して1つの操作のみを実行します。getS()の結果を出力するだけで、oをTにキャストするため、これはタイプTのクラスに対してのみ実行できます。

インターフェイスを一般的なビジタースタイルのクラスに適用したい場合、DoInterface.perform関数を使用したクラスは、次のようなifelseifステートメントで大きくなる可能性があります。

    public void visit(Object o) {
        if (o instanceof File)
            visitFile((File)o);
        else if (o instanceof Directory)
            visitDirectory((Directory)o);
        else if (o instanceof X)
            // ...
    }

これはObjectを使用するため、実行時にのみ表示されるエラーを作成する可能性のある任意のタイプの呼び出し元を許可します。訪問者は、データ構造のタイプごとに「visitType」関数を作成することでこれを回避します。データ構造内のクラスは、訪問者のどの関数を呼び出すかを知る責任があります。マッピングは、Visitorクラスをコールバックするaccept関数を実装するデータ構造の各クラスによって実行されます。タイプの関数がビジターに存在しない場合、コンパイルエラーが発生します。acceptメソッドは次のようになります。

    @Override
    public void accept(FileSystemVisitor v) {
        v.visitFile(this);
    }

ビジターパターンの問題の一部は、サンプルで実際にそれを正しく行うには非常に多くのコードが必要になることです。他のコードに気を取られやすいので、多くの人がそれを受け取らないのはこのためだと思います。訪問者をより明確に使用する方法を示す簡単なファイルシステムサンプルを作成しました。いくつかのファイルとディレクトリを含むコンポジットを作成し、階層に対して2つの操作を実行します。実際には、このパターンを正当化するために3つ以上のデータクラスと2つの操作が必要になる可能性がありますが、これは単なる例です。

public class VisitorSample {
    //
        public abstract class FileSystemItem {
            public abstract String getName();
            public abstract int getSize();
            public abstract void accept(FileSystemVisitor v);
        }
    //  
        public abstract class FileSystemItemContainer extends FileSystemItem {
            protected java.util.ArrayList<FileSystemItem> _list = new java.util.ArrayList<FileSystemItem>();
    //              
            public void addItem(FileSystemItem item)
            {
                _list.add(item);
            }
    //
            public FileSystemItem getItem(int i)
            {
                return _list.get(i);
            }
    //          
            public int getCount() {
                return _list.size();
            }
    //      
            public abstract void accept(FileSystemVisitor v);
            public abstract String getName();
            public abstract int getSize();
        }
    //  
        public class File extends FileSystemItem {
    //
            public String _name;
            public int _size;
    //      
            public File(String name, int size) {
                _name = name;
                _size = size;
            }
    //      
            @Override
            public void accept(FileSystemVisitor v) {
                v.visitFile(this);
            }
    //
            @Override
            public String getName() {
                return _name;
            }
    //
            @Override
            public int getSize() {
                return _size;
            }
        }
    //  
        public class Directory extends FileSystemItemContainer {
    //
            private String _name;
    //      
            public Directory(String name) {
                _name = name;
            }
    //      
            @Override
            public void accept(FileSystemVisitor v) {
                v.visitDirectory(this);
            }
    //
            @Override
            public String getName() {
                return _name;
            }
    //
            @Override
            public int getSize() {
                int size = 0;
                for (int i = 0; i < _list.size(); i++)
                {
                    size += _list.get(i).getSize();
                }
                return size;
            }       
        }
    //  
        public abstract class FileSystemVisitor {
    //      
            public void visitFile(File f) { }
            public void visitDirectory(Directory d) { }
    //
            public void vistChildren(FileSystemItemContainer c) {
                for (int i = 0; i < c.getCount(); i++)
                {
                    c.getItem(i).accept(this);
                }
            }
        }
    //  
        public class ListingVisitor extends FileSystemVisitor {
    //      
            private int _indent = 0;
    //      
            @Override
            public void visitFile(File f) {
                for (int i = 0; i < _indent; i++)
                    System.out.print(" ");
                System.out.print("~");
                System.out.print(f.getName());
                System.out.print(":");
                System.out.println(f.getSize());
            }
    //  
            @Override
            public void visitDirectory(Directory d) {
                for (int i = 0; i < _indent; i++)
                    System.out.print(" ");  
                System.out.print("\\");
                System.out.print(d.getName());
                System.out.println("\\");
    //          
                _indent += 3;
                vistChildren(d);
                _indent -= 3;
            }
        }
    //  
        public class XmlVisitor extends FileSystemVisitor {
    //      
            private int _indent = 0;
    //      
            @Override
            public void visitFile(File f) {
                for (int i = 0; i < _indent; i++)
                    System.out.print(" ");
                System.out.print("<file name=\"");
                System.out.print(f.getName());
                System.out.print("\" size=\"");
                System.out.print(f.getSize());
                System.out.println("\" />");
            }
    //  
            @Override
            public void visitDirectory(Directory d) {
                for (int i = 0; i < _indent; i++)
                    System.out.print(" ");
                System.out.print("<directory name=\"");
                System.out.print(d.getName());
                System.out.print("\" size=\"");
                System.out.print(d.getSize());
                System.out.println("\">");
    //          
                _indent += 4;
                vistChildren(d);
                _indent -= 4;
    //          
                for (int i = 0; i < _indent; i++)
                    System.out.print(" ");
                System.out.println("</directory>");
            }
        }
    //  
        public static void main(String[] args) {
            VisitorSample s = new VisitorSample();
    //      
            Directory root = s.new Directory("root");
            root.addItem(s.new File("FileA", 163));
            root.addItem(s.new File("FileB", 760));
            Directory sub = s.new Directory("sub");
            root.addItem(sub);
            sub.addItem(s.new File("FileC", 401));
            sub.addItem(s.new File("FileD", 543));
            Directory subB = s.new Directory("subB");
            root.addItem(subB);
            subB.addItem(s.new File("FileE", 928));
            subB.addItem(s.new File("FileF", 238));
    //      
            XmlVisitor xmlVisitor = s.new XmlVisitor();
            root.accept(xmlVisitor);
    //      
            ListingVisitor listing = s.new ListingVisitor();
            root.accept(listing);
        }
    }
于 2008-10-19T22:51:41.470 に答える
4

これらの例の最も重要な違いは、訪問者の場合、コンパイル時の具象型「this」を保持することです。これにより、呼び出されるメソッドが具象データ型と訪問者の実装の両方に依存するダブルディスパッチを使用できます。ダブルディスパッチは、呼び出されるメソッドがレシーバーとメソッドのパラメーターのタイプに依存する多重ディスパッチの特殊なケースです。Javaはもちろんシングルディスパッチですが、他のいくつかの言語はマルチディスパッチをサポートしています。

ビジターパターンの背後にある基本的な推進力は、具象ノードでインターフェースを使用することにより、複合データ構造に追加する必要のあるすべての操作がすべてのノードを変更する必要があることです。ビジターパターンは、ノードで汎用(静的)パターンを使用するため、動的に操作を追加するのは簡単です。欠点は、すべての操作訪問者が影響を受けるため、データ構造の変更(具象ノードの追加または削除による)がより困難になることです。

一般に、このトレードオフは、データ構造自体を変更するよりもデータ構造全体に操作を拡張する方が頻繁であるため、より適切に一致します。これは、訪問者の使い方とたくさんの考慮事項についての私の長い文章です:

既存のコードを壊すことなく、操作を追加するか、データ構造を拡張するかの両方を実行できるパターンがあるかどうかをかなり疑問に思うかもしれません。これは、PhilipWadlerによって造られたTheExpressionProblemとして知られています。あなたはこれともっとここにいくつかのリンクを見つけることができます:

于 2008-10-12T02:11:40.823 に答える
4

2つのこと:

  • あなたの例では、2 つのメソッドが必要です。perfomsetInterface. _ perfomビジター パターンでは、通常は と呼ばれる1 つのメソッドのみが必要ですaccept
  • 複数の「実行者」が必要な場合は、メソッドを介して実行者setInterfaceをそれぞれに設定する必要があります。これにより、クラスを不変にすることができなくなります。
于 2008-10-11T19:04:38.423 に答える
0

すぐにわかる唯一のことは、インターフェイスを格納することで、それを呼び出すのに 1 回ではなく 2 回の操作を行う必要があるということです。インターフェイスが設定されたら同じアクションを繰り返し実行する場合、これは理にかなっていると思いますが、標準の Visitor に固執して同じことを達成できると思います。

于 2008-10-11T18:17:30.733 に答える