Visitor pattern implementation in case of source code un-availability

You could combine a Wrapper and Visitor to solve your problems. Using the wrapper to add a visit method allows you to increase the usability of these objects. Of course you get the full advantages (less dependency on the legacy classes) and disadvantages (additional objects) of a wrapper.


Here's a worked-up example in JAVA (because it is pretty strict, does not do double-dispatch by itself, and I'm quite familiar with it):

1) Your legacy Objects

Assuming you have your legacy objects Legacy1 and Legacy2which you cannot change, which have specific business methods:

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

and

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

2) Prepare the Wrapper

You just wrap them in a VisitableWrapper which has a visit method that takes your visitor, like:

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

With the following implementations:

public class Legacy1Wrapper implements VisitableWrapper {

    private final Legacy1 legacyObj;

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

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

and

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) Visitor, at the ready!

Then your own Visitors can be set to visit the wrapper like so:

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

With an implementation like so:

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) Unleash the power

Finally in your code, this framework would work like this:

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

The expected output:

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

Drawbacks:

  1. Quite a lot of boilerplate. Use Lombok if you develop in Java.
  2. Quite a lot of wrapper objects instances. May or may not be a problem for you.
  3. You need to know the specific type of the objects beforehand. This implies you know their subtype, they aren't bundles in a List. If that's the case, you have no other option but to use reflection.