在源代码不可用的情况下实现访问者模式

时间:2016-08-12 14:34:33

标签: java design-patterns visitor-pattern

考虑Visitor_pattern

的原因之一
  

这种分离的实际结果是能够在不修改这些结构的情况下向现有对象结构添加新操作。

假设您没有第三方库的源代码,并且您在相关对象上添加了一个操作。

由于您没有对象,因此无法修改您的元素(第三方类)以添加访问者。

enter image description here

在这种情况下,无法进行双重调度。

那么通常首选哪个选项?

Option 1: 在第三方类之上扩展一个继承层次结构并实现模式如图所示,带双重调度?

对于扩展A类的给定B类层次结构,我将添加

ElementA extends A
ElementB extends B

现在,ConcreteElements派生自ElementA而非A类。

缺点:课程数量会增加。

Option 2: 将Visitor类用作中央帮助程序类,并通过单个调度完成工作。

缺点:根据UML图,我们并没有真正关注Visitor模式。

如果我错了,请纠正。

6 个答案:

答案 0 :(得分:10)

您可以将 Wrapper 访问者结合起来解决您的问题。 使用包装器添加 visit方法可以增加这些对象的可用性。当然,你可以获得包装器的全部优点(对遗留类的依赖性较小)和缺点(附加对象)。

这是JAVA中一个经过深思熟虑的例子(因为它非常严格,不会单独进行双重调度,而且我非常熟悉它):

1)旧版对象

假设您有无法更改的旧对象Legacy1Legacy2,这些对象具有特定的业务方法:

public final class Legacy1 {
    public void someBusinessMethod1(){
        ...
    }
}

public final class Legacy2 {
    public void anotherBusinessMethod(){
        ...
    }
}

2)准备包装

您只需将它们包裹在 VisitableWrapper 中,其中visit方法会将访问者视为:

public interface VisitableWrapper {
    public void accept(Visitor visitor);
}

通过以下实施:

public class Legacy1Wrapper {

    private final Legacy1 legacyObj;

    public Legacy2Wrapper(Legacy1 original){
        this.legacyObj = original;
    }

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

public class Legacy2Wrapper {

    private final Legacy2 legacyObj;

    public Legacy2Wrapper(Legacy2 original){
        this.legacyObj = original;
    }

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

3)访客,准备好了!

然后您可以将自己的访问者设置为访问包装器,如下所示:

public interface Visitor {
     public void visit(Legacy1 leg);
     public void visit(Legacy2 leg);
}

使用类似的实现:

public class SomeLegacyVisitor{

    public void visit(Legacy1 leg){
        System.out.println("This is a Legacy1! let's do something with it!");
        leg.someBusinessMethod1();
    }

    public void visit(Legacy2 leg){
        System.out.println("Hum, this is a Legacy 2 object. Well, let's do something else.");
        leg.anotherBusinessMethod();
    }
}

4)释放电量

最后在您的代码中,此框架将如下工作:

public class TestClass{
    // Start off with some legacy objects
    Legacy1 leg1 = ...
    Legacy2 leg2 = ...

    // Wrap all your legacy objects into a List:
    List<VisitableWrapper> visitableLegacys = new ArrayList<>();
    visitableLegacys.add(new Legacy1Wrapper(legacy1));
    visitableLegacys.add(new Legacy2Wrapper(legacy2));


    Visitor visitor = new SomeLegacyVisitor(); // Use any of your visitor implementation !
    for(VisitableWrapper wrappedLegacy: visitableLegacys){
        wrappedLegacy.accept(visitor);
    }
}

预期产出:

This is a Legacy1! let's do something with it!
Hum, this is a Legacy 2 object. Well, let's do something else.

<强>缺点:

  1. 相当多的样板。如果使用Java开发,请使用Lombok
  2. 相当多的包装器对象实例。可能或可能不是你的问题。
  3. 您需要事先知道对象的具体类型。这意味着您知道它们的子类型,它们不是List中的捆绑包。如果是这种情况,除了使用反射之外别无选择。

答案 1 :(得分:0)

应该有可能在某些层次结构的类中添加新功能,而无需更改基类接口。各种可能的行为应该是不变的,而不同类的操作应该以不同的方式执行。

访客模式允许将所有操作集中在一个类中。可能有很多Concrete Element类(来自图表),但是对于每个类,都会在visit()类中实现Concrete Visitor方法,它将定义自己的算法。

Element类的每个子类的方法的定义和实现:

public interface Visitor {
    void visit(Element element);
}

public class ConcreteVisitor implements Visitor {
    public void visit(Element element) {
        // implementation
    }
}

访客模式可以通过新类通过其方法实现实现此接口,轻松扩展新操作。

以下结构封装了Element类:

public lass ObjectStructure {
    private Element element;
    // some methods
}

ObjectStructure类可以聚合Element的一个或多个实例。 Visitor作出的陈述:

public interface Element {
    void accept(Visitor visitor);
}

在具体实体中实现accept()方法:

public class ConcreteElement implements Element {
    public void accept(Visitor visitor) {
        visitor.visit();
    }
}

使用访客模式可以从巨大的逻辑功能或复杂的配置中保存Element层次结构。

在定义新的Visitor子类时,最好将功能添加到所有层次结构类中。但可能存在问题:visit() 应覆盖每种层次结构类型。为避免这种情况,最好定义AbstractVisitor类,并将所有visit()方法体留空。

结论:当类型Element的类层次结构保持不变时,使用此模式是很好的。如果新类添加,则Visitor类的类通常会发生相当大的变化。

答案 2 :(得分:0)

我的答案与Michael von Wenckstern非常相似,改进后我们有一个名为accept的方法(更像标准模式),我们处理未知的具体类 - 那里&#39我们无法保证在某种程度上我们还没有看到一个具体的实施方式,而不会出现在类路径上。 我的访客也允许返回值。

我还为visit方法使用了更详细的名称 - 包括方法名称中的类型,但这不是必需的,您可以将它们全部调用{{1} }。

visit

答案 3 :(得分:0)

首先,我必须对遗留代码做一些假设,因为您没有提供有关它的更多详细信息。让我们说我需要在Legacy添加新方法而不重新实现所有内容。我就是这样做的:

public interface LegacyInterface {
    void A();
}

public final class LegacyClass implements LegacyInterface {
    @Override
    public void A() {
        System.out.println("Hello from A");
    }
}

首先扩展&#34;合同&#34;

public interface MyInterface extends LegacyInterface {
    void B();
}

并在&#34; decorated&#34;方式

public final class MyClass implements MyInterface {
    private final LegacyInterface origin;

    public MyClass(LegacyInterface origin) {
        this.origin = origin;
    }

    @Override
    public void A() {
        origin.A();
    }

    @Override
    public void B() {
        System.out.println("Hello from B");
    }
}

关键点是MyInterface extends LegacyInterface:这是保证,实施将从遗留代码中的服务和您的个人添加中受益。

用法

MyInterface b = new MyClass(new LegacyClass());

答案 4 :(得分:0)

我认为最好的方法是 Option 1: 在第三方类之上扩展一个继承层次结构,并使用双重调度实现访问者模式。

问题是您需要的其他类的数量,但这可以通过动态包装装饰器来解决。 Wrapper Decorator是一种向已有的obejcts添加接口实现,方法和属性的方法:How to implement a wrapper decorator in Java?

通过这种方式,您需要Visitor界面并将visit(L legacy)方法放在那里:

public interface Visitor<L> {
    public void visit(L legacy);
}

在AcceptInterceptor中,您可以放置​​accept方法的代码

public class AcceptInterceptor {

    @RuntimeType
    public static Object intercept(@This WrappedAcceptor proxy, @Argument(0) Visitor visitor) throws Exception {
        visitor.visit(proxy);
    }
}

WrappedAcceptor接口定义了接受访问者以及设置和检索包装对象的方法

interface WrappedAcceptor<V> {
   Object getWrapped();
   void setWrapped(Object wrapped);
   void accept(V visitor); 
}

最后,实用程序代码围绕任何对象创建Wrapper:

Class<? extends Object> proxyType = new ByteBuddy()
 .subclass(legacyObject.getClass(), ConstructorStrategy.Default.IMITATE_SUPER_TYPE_PUBLIC)
 .method(anyOf(WrappedAcceptor.class.getMethods())).intercept(MethodDelegation.to(AcceptInterceptor.class))
 .defineField("wrapped", Object.class, Visibility.PRIVATE)
 .implement(WrappedAcceptor.class).intercept(FieldAccessor.ofBeanProperty())
 .make()
 .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
 .getLoaded();
WrappedAcceptor wrapper = (WrappedAcceptor) proxyType.newInstance();
wrapper.setWrapped(legacyObject);

答案 5 :(得分:-1)

如果您的图书馆没有accept方法,则需要使用instanceof。 (通常在Java中进行两次单调度以模拟双调度;但是在这里我们使用instanceof来模拟双调度。)

以下是示例:

interface Library {
  public void get1();
  public void get2();
}

public class Library1 implements Library {
  public void get1() { ... }
  public void get2() { ... }
}

public class Library2 implements Library {
  public void get1() { ... }
  public void get2() { ... }
}

interface Visitor {
   default void visit(Library1 l1) {}
   default void visit(Library2 l2) {}

   default void visit(Library l) {
      // add here instanceof for double dispatching
      if (l instanceof Library1) {
          visit((Library1) l);
      }
      else if (l instanceof Library2) {
          visit((Library2) l);
      }
   }
}

// add extra print methods to the library
public class PrinterVisitor implements Visitor {
   void visit(Library1 l1) {
       System.out.println("I am library1");
   } 
   void visit(Library2 l2) {
       System.out.println("I am library2");
   }        
}

现在你可以写任何方法:

Library l = new Library1();
PrinterVisitor pv = new PrinterVisitor();
pv.visit(l);

它将打印给你&#34;我是library1&#34 ;;