ホームページ  >  記事  >  Java  >  Java は、ビジター モードでリフレクションを使用してサンプル コード共有を実装します。

Java は、ビジター モードでリフレクションを使用してサンプル コード共有を実装します。

黄舟
黄舟オリジナル
2017-03-23 11:13:331205ブラウズ

コレクション型は、オブジェクト指向のプログラミングで非常に一般的に使用されており、コード関連の問題も引き起こします。例: 「コレクション内のさまざまなタイプのオブジェクトを操作するにはどうすればよいですか? 1 つの方法は、コレクション内の各要素を反復処理し、その型に基づいて特定の操作を実行することです。これは、特にコレクション内の要素の型が分からない場合に印刷する必要があります。コレクション Element の要素を使用するには、次のようなメソッドを記述できます:

public void messyPrintCollection(Collection collection) {
    Iterator iterator = collection.iterator()
    while (iterator.hasNext())
        System.out.println(iterator.next().toString())
}

これは、Object.toString() メソッドを呼び出してオブジェクトを出力するだけです。しかし、コレクションが を含むベクトルの場合はどうなるでしょうか?ハッシュテーブルですか? コレクションから返されるオブジェクトの型を確認する必要があります:

public void messyPrintCollection(Collection collection) {
    Iterator iterator = collection.iterator()
    while (iterator.hasNext()) {
        Object o = iterator.next();
        if (o instanceof Collection)
            messyPrintCollection((Collection)o);
        else
            System.out.println(o.toString());
        }
}

さて、これで埋め込みコレクション オブジェクトを処理できますが、他のオブジェクトから返される

String

は必要なものではありません。 ? 文字列オブジェクトが引用符で囲まれている場合、Float オブジェクトの後に f を追加すると、コードがさらに複雑になります: rreee コードがすぐに乱雑になるのを回避するにはどうすればよいですか?

Visitor パターン

を実装するには、Visitor インターフェイス を作成し、訪問されたコレクション オブジェクトの Visitable インターフェイスを作成する必要があります。これらの 2 つのインターフェイスはおおよそ次のとおりです。

public void messyPrintCollection(Collection collection) {
    Iterator iterator = collection.iterator()
    while (iterator.hasNext()) {
        Object o = iterator.next();
        if (o instanceof Collection)
            messyPrintCollection((Collection)o);
        else if (o instanceof String)
            System.out.println("'"+o.toString()+"'");
        else if (o instanceof Float)
            System.out.println(o.toString()+"f");
        else
            System.out.println(o.toString());
    }
}

特定の String クラスの場合、次のように実装できます:

public interface Visitor
{
    public void visitCollection(Collection collection);
    public void visitString(String string);
    public void visitFloat(Float float);
}
public interface Visitable
{
    public void accept(Visitor visitor);
}
accept メソッドで、さまざまな型に応じてビジター内の対応するメソッドを呼び出します。 :

Visitor の具体的な実装は次のとおりです。次のように:

public class VisitableString implements Visitable
{
    private String value;
    public VisitableString(String string) {
        value = string;
    }
    public void accept(Visitor visitor) {
        visitor.visitString(this);
    }
}

そのとき、VisitableFloat クラスと VisitableCollection クラスを実装し、適切な訪問者メソッドを呼び出す限り、多数の if-else 構造を含む mesyPrintCollection メソッドを削除し、非常に巧妙な方法で実現できます。同じ関数です。visitCollection() メソッドは Visitable.accept(this) を呼び出し、次に訪問者の正しいメソッドを呼び出します。これは二重ディスパッチです。訪問者は Visitable クラスのメソッドを呼び出します。 Visitor クラス内で visitor.visitString(this)

訪問者を実装すると if-else ステートメントは消えますが、Visitable インターフェイスを実装するクラスにパッケージ化された String と Float という多くの追加コードを導入する必要がありますが、これは一般的に面倒です。 Visitable オブジェクトのみを含むように制限できるため、問題はありません。ただし、VisitableInteger などの新しい Visitable 型を追加する必要がある場合は、さらに多くの作業が必要になります。これは、訪問者パターンの大きな欠点です。新しい Visitor 型を追加する場合は、Visitor インターフェイスと Visitor インターフェイス メソッドを実装するすべてのクラスを変更する必要があります。 Visitor をインターフェイスとして設計することはできません。代わりに、

no-operation

を備えた抽象基本クラスとして Visitor を設計できます。これは、Java GUI のアダプタ クラスに非常に似ています。これを行う場合の問題は、単一の

継承

を使い果たすことになり、一般的な状況として、StringWriter クラスの継承など、他の関数を実装するために継承も使用したい場合があります。これも、Visitable インターフェイスを実装するオブジェクトにのみ正常にアクセスできます。

幸いなことに、Java では訪問者のパターンをより柔軟にすることができ、必要に応じて Visitable オブジェクトを追加できます。それを達成するにはどうすればよいでしょうか?答えは、リフレクションを使用することです。リフレクションを使用する ReflectiveVisitor インターフェイスに必要なメソッドは 1 つだけです:

public class PrintVisitor implements Visitor
{
    public void visitCollection(Collection collection) {
        Iterator iterator = collection.iterator();
        while (iterator.hasNext()) {
            Object o = iterator.next();
            if (o instanceof Visitable)
                ((Visitable)o).accept(this);
    }
    public void visitString(String string) {
        System.out.println("'"+string+"'");
    }
    public void visitFloat(Float float) {
        System.out.println(float.toString()+"f");
    }
}
さて、上記は非常に簡単です。 Visitable インターフェイスは今のところ使用されません。これについては後で説明します。ここで、リフレクションを使用して PrintVisitor クラスを実装します。
public interface ReflectiveVisitor {
    public void visit(Object o);
}
これで、Visitable ラッパー クラス (プリミティブ型 String と Float をラップする) を使用する必要がなくなりました。 visit() に直接アクセスすると、正しいメソッドが呼び出されます。 visit() の利点の 1 つは、適切と思われるメソッドをディスパッチすることです。これは必ずしもリフレクションを使用するわけではなく、まったく異なるメカニズムを使用する可能性があります。

新しい PrintVisitor クラスには、Collection、String、Float に対応する操作メソッドがあり、処理できない型については、catch ステートメントを通じてキャプチャできます。処理できない型の場合は、visit() メソッドを拡張することで、すべてのスーパークラスの処理を試みることができます。まず、新しいメソッド getMethod(Class c) を追加します。戻り値はトリガーできるメソッドです。クラス c のすべての親クラスとインターフェースを

検索

して、一致するメソッドを見つけます。

protected Method getMethod(Class c) {
    Class newc = c;
    Method m = null;
    // Try the superclasses
    while (m == null && newc != Object.class) {
        String method = newc.getName();
        method = "visit" + method.substring(method.lastIndexOf('.') + 1);
        try {
            m = getClass().getMethod(method, new Class[] {newc});
        } catch (NoSuchMethodException e) {
            newc = newc.getSuperclass();
        }
    }
    // Try the interfaces.  If necessary, you
    // can sort them first to define 'visitable' interface wins
    // in case an object implements more than one.
    if (newc == Object.class) {
        Class[] interfaces = c.getInterfaces();
        for (int i = 0; i < interfaces.length; i++) {
            String method = interfaces[i].getName();
            method = "visit" + method.substring(method.lastIndexOf(&#39;.&#39;) + 1);
            try {
                m = getClass().getMethod(method, new Class[] {interfaces[i]});
            } catch (NoSuchMethodException e) {}
        }
    }
    if (m == null) {
        try {
            m = thisclass.getMethod("visitObject", new Class[] {Object.class});
        } catch (Exception e) {
            // Can&#39;t happen
        }
    }
    return m;
}

这看上去很复杂,实际上并不。大致来说,首先根据传入的class名称搜索可用方法;如果没找到,就尝试从父类搜索;如果还没找到,就从接口中尝试。最后,(仍没找到)可以使用visitObject()作为默认方法。

由于大家对传统的访问者模式比较熟悉,这里沿用了之前方法命名的惯例。但是,有些人可能注意到,把所有的方法都命名为“visit”并通过参数类型不同来区分,这样更高效。然而,如果你这么做,你必须把visit(Object o)方法的名称改为其他,比如dispatch(Object o)。否则,(当没有对应处理方法时),你无法退回到默认的处理方法,并且当你调用visit(Object o)方法时,为了确保正确的方法调用,你必须将参数强制转化为Object。

为了利用getMethod()方法,现在需要修改一下visit()方法。

public void visit(Object object) {
    try {
        Method method = getMethod(getClass(), object.getClass());
        method.invoke(this, new Object[] {object});
    } catch (Exception e) { }
}

现在,visitor类更加强大了——可以传入任意的对象并且有对应的处理方法。另外,有一个默认处理方法,visitObject(Object o),的好处就是就可以捕捉到任何没有明确说明的类型。再稍微修改下,你甚至可以添加一个visitNull()方法。

我仍保留Visitable接口是有原因的。传统访问者模式的另一个好处是它可以通过Visitable对象控制对象结构的遍历顺序。举例来说,假如有一个实现了Visitable接口的类TreeNode,它在accept()方法中遍历自己的左右节点。

public void accept(Visitor visitor) {
    visitor.visitTreeNode(this);
    visitor.visitTreeNode(leftsubtree);
    visitor.visitTreeNode(rightsubtree);
}

这样,只要修改下Visitor类,就可以通过Visitable类控制遍历:

public void visit(Object object) throws Exception
{
    Method method = getMethod(getClass(), object.getClass());
    method.invoke(this, new Object[] {object});
    if (object instanceof Visitable)
    {
        callAccept((Visitable) object);
    }
}
public void callAccept(Visitable visitable) {
    visitable.accept(this);
}

如果你实现了Visitable对象的结构,你可以保持callAccept()不变,就可以使用Visitable控制的对象遍历。如果你想在visitor中遍历对象结构,你只需重写allAccept()方法,让它什么都不做。

当使用几个不同的visitor去操作同一个对象集合时,访问者模式的力量就会展现出来。比如,当前有一个解释器、中序遍历器、后续遍历器、XML编写器以及SQL编写器,它们可以处理同一个对象集合。我可以轻松地为这个集合再写一个先序遍历器或者一个SOAP编写器。另外,它们可以很好地兼容它们不识别的类型,或者我愿意的话可以让它们抛出异常。

总结

使用Java反射,可以使访问者模式提供一种更加强大的方式操作对象结构,可以按照需求灵活地增加新的Visitable类型。我希望在你的编程之旅中可以使用访问者模式。

以上がJava は、ビジター モードでリフレクションを使用してサンプル コード共有を実装します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。