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

Visitor pattern implementation in case of source code un-availability

考虑Visitor_pattern的原因之一:

A practical result of this separation is the ability to add new operations to existing object structures without modifying those structures.

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

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

在这种情况下,双重调度是不可能的。

那么通常首选哪个选项?

Option 1: 在第三方之上再扩展一层继承层次结构 class 并实现如图所示的双分派模式?

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

ElementA extends A
ElementB extends B

现在 ConcreteElements 派生自 ElementA 而不是 class A。

缺点:classes 的数量将会增加。

Option 2: 使用访客 class 中央助手 class 并通过一次调度完成工作。

缺点:我们并没有真正按照 UML 图遵循访问者模式。

如有错误请指正

应该可以在不更改基础 class 界面的情况下,向某些层次结构的 classes 添加新功能。各种可能的行为应该是不变的,而针对不同 classes 的操作应该以不同的方式执行。

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

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

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

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

Visitor Pattern 很容易通过新的 class 方法实现来实现这个接口来扩展新的操作。

下面的结构封装了Element class:

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

ObjectStructure class 可以聚合 Element 的一个或多个实例。 Visitor 作用于的演示文稿:

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

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

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

使用 Visitor Pattern 允许从庞大的逻辑功能或复杂的配置中保存 Element 层次结构。

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

结论: 当类型 Element 的 class 层次结构保持不变时,使用此模式很好。如果添加新的 classes,通常会对 Visitor 类型的 classes 进行相当大的更改。

如果您的图书馆没有 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);

它会打印给你 "I am library1";

您可以结合使用 WrapperVisitor 来解决您的问题。 使用包装器 添加 一个 visit 方法可以增加这些对象的可用性。当然,您可以获得包装器的全部优点(对遗留 类 的依赖性较低)和缺点(其他对象)。


这是JAVA中的一个改进示例(因为它非常严格,不会自己进行双重调度,我对此非常熟悉):

1) 您的遗留对象

假设您有遗留对象Legacy1Legacy2无法更改,它们具有特定的业务方法:

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

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

2) 准备包装纸

你只需将它们包装在一个 VisitableWrapper 中,它有一个 visit 方法来获取你的 visitor,例如:

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

具有以下实现:

public class Legacy1Wrapper implements VisitableWrapper {

    private final Legacy1 legacyObj;

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

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

public class Legacy2Wrapper implements VisitableWrapper {

    private final Legacy2 legacyObj;

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

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

3) 访客,准备就绪!

然后你自己的 Visitors 可以设置为像这样访问包装器:

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));

    // Use any of your visitor implementations!
    Visitor visitor = new SomeLegacyVisitor();
    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.
  2. 中开发,请使用 Lombok
  3. 相当多的包装对象实例。对您来说可能是问题,也可能不是问题。
  4. 您需要事先了解对象的具体类型。这意味着您知道它们的子类型,它们不是列表中的捆绑包。如果是这种情况,您别无选择,只能使用反射。

我的回答与 Michael von Wenckstern 的非常相似,改进之处在于我们有一个命名的 accept 方法(更像标准模式)并且我们处理未知的具体 类 -- 有不能保证在某些时候我们以前没有见过的具体实现不会出现在类路径中。 我的访问者还允许 return 值。

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

// these classes cannot be modified and do not have source available

class Legacy {
}

class Legacy1 extends Legacy {
}
class Legacy2 extends Legacy {
}

// this is the implementation of your visitor    

abstract class LegacyVisitor<T> {
    abstract T visitLegacy1(Legacy1 l);
    abstract T visitLegacy2(Legacy2 l);

    T accept(Legacy l) {
        if (l instanceof Legacy1) {
            return visitLegacy1((Legacy1)l);
        } else if (l instanceof Legacy2) {
            return visitLegacy2((Legacy2)l);
        } else {
            throw new RuntimeException("Unknown concrete Legacy subclass:" + l.getClass());
        }
    }
}
public class Test {
    public static void main(String[] args) {
        String s = new LegacyVisitor<String>() {

            @Override
            String visitLegacy1(Legacy1 l) {
                return "It's a 1";
            }

            @Override
            String visitLegacy2(Legacy2 l) {
                return "It's a 2";
            }
        }.accept(new Legacy1());

        System.out.println(s);
    }
}

首先,我必须对遗留代码做出一些假设,因为您没有提供太多相关细节。假设我需要向 Legacy 添加一个新方法而不重新实现所有内容。我会这样做:

public interface LegacyInterface {
    void A();
}

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

首先扩展 "contract"

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

并以“decorated”的方式实施

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());

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

问题是您需要的额外 classes 的数量,但这可以通过动态包装装饰器来解决。 Wrapper Decorator 是一种向现有对象添加接口实现、方法和属性的方法:

通过这种方式,您需要 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); 
}

最后是围绕任何对象创建包装器的实用程序代码:

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);