使用访客模式时的匿名或真实类定义?

时间:2011-08-30 14:05:22

标签: java design-patterns visitor-pattern

当您使用访客模式并且需要在访问者方法中获取变量时,如何继续?

我看到两种方法。第一个使用匿名类:

// need a wrapper to get the result (which is just a String)
final StringBuild result = new StringBuilder();
final String concat = "Hello ";

myObject.accept(new MyVisitor() {

    @Override
    public void visit(ClassA o)
    {
        // this concatenation is expected here because I've simplified the example
        // normally, the concat var is a complex object (like hashtable) 
        // used to create the result variable 
        // (I know that concatenation using StringBuilder is ugly, but this is an example !)
        result.append(concat + "A");
    }

    @Override
    public void visit(ClassB o)
    {
        result.append(concat + "B");
    }
});

System.out.println(result.toString());

优点&缺点:

  • 优点:您不需要为这个小行为创建一个类文件
  • 缺点:在这种情况下我不喜欢“final”关键字:匿名类的可读性较差,因为它调用外部变量,您需要使用包装器来获取请求的值(因为使用关键字final,您无法重新分配变量)

另一种方法是做一个外部访客类:

public class MyVisitor
{
    private String result;
    private String concat;

    public MyVisitor(String concat)
    {
        this.concat = concat;
    }

    @Override
    public void visit(ClassA o)
    {
        result = concat + "A";
    }

    @Override
    public void visit(ClassB o)
    {
        result = concat + "B";
    }

    public String getResult()
    {
        return result;
    }
}

MyVisitor visitor = new MyVisitor("Hello ");
myObject.accept(visitor);
System.out.println(visitor.getResult());

优点&缺点:

  • 优点:所有变量都在一个干净的范围内定义,您不需要包装器来封装请求的变量
  • 缺点:需要一个外部文件,getResult()方法必须在accept方法之后调用,这是非常难看的,因为你需要知道函数调用顺序才能正确使用访问者

你,在这种情况下你的方法是什么?首选方法?另一个想法?

5 个答案:

答案 0 :(得分:3)

嗯,这两种方法都是有效的和imo,这实际上取决于你是否想重用代码。顺便说一下,你的最后一个'Con'点并不完全有效,因为你不需要一个'外部文件'来声明一个类。它很可能是一个内在阶级...

那就是说,我使用访客的方式是这样的:

public interface IVisitor<T extends Object> {
    public T visit(ClassA element) throws VisitorException;
    public T visit(ClassB element) throws VisitorException;
}

public interface IVisitable {
    public <T extends Object> T accept(final IVisitor<T> visitor) throws VisitorException;
}

public class MyVisitor implements IVisitor<String> {
    private String concat;

    public MyVisitor(String concat) {
        this.concat = concat;
    }

    public String visit(ClassA classA) throws VisitorException {
        return this.concat + "A";
    }

    public String visit(ClassB classB) throws VisitorException {
        return this.concat + "B";
    }
}

public class ClassA implements IVisitable {
    public <T> T accept(final IVisitor<T> visitor) throws VisitorException {
        return visitor.visit(this);
    }
}

public class ClassB implements IVisitable {
    public <T> T accept(final IVisitor<T> visitor) throws VisitorException {
        return visitor.visit(this);
    }
}

// no return value needed?
public class MyOtherVisitor implements IVisitor<Void> {
    public Void visit(ClassA classA) throws VisitorException {
        return null;
    }

    public Void visit(ClassB classB) throws VisitorException {
        return null;
    }
}

这样,访问过的对象不知道访问者想要对他们做什么,但他们确实返回了访问者想要返回的内容。您的访问者甚至可以通过抛出异常来“失败”。

几年前我写了第一个版本,到目前为止,它在每种情况下都适用于我。

免责声明:我只是一起攻击,质量(甚至编译)无法保证。但是你明白了...... :)

答案 1 :(得分:1)

我在第二个例子中没有看到interface正在实施,但我相信它就在那里。我会添加到你的界面(或创建一个子界面)上面有getResult()方法。

这对示例1和2都有帮助。您不需要1中的包装器,因为您可以定义getResult()方法以返回所需的结果。在示例2中,因为getResult()是您界面的一部分,所以没有您需要知道的功能。

我的偏好是创建一个新类,除非该类的每个变体只使用一次。在这种情况下,我会匿名内联。

答案 2 :(得分:1)

清洁设计的角度来看,第二种方法是出于你已经陈述的相同的原因而优先考虑。

在正常的TDD循环中,我将从一个匿名类开始,稍后重构一遍。但是,如果只在那个地方需要访问者并且其复杂性与您在示例中提供的那些(即不复杂)的复杂性相匹配,那么我会将其悬挂并重构为一个单独的类,如果需要的话, (例如,出现了另一个用例,访问者/周围类的复杂性增加了)。

答案 3 :(得分:1)

我建议使用第二种方法。让访问者参加完整的课程也可以达到文档和清洁代码的目的。我不同意你提到的方法的缺点。假设你有一个arraylist,你没有添加任何元素并做一个get,你肯定会得到一个null,但这并不意味着它必然是错误的。

答案 4 :(得分:0)

访问者模式的一个要点是允许多种访问者类型。如果你创建一个匿名类,你就会打破这种模式。

您应该将accept方法更改为

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

由于您将this传递给访问者,this是访问过的对象,访问者可以根据标准访问规则访问该对象的属性。