views:

100

answers:

4

Hi.

I need help to generify and implement the visitor pattern. We are using tons of instanceof and it is a pain. I am sure it can be modified, but I am not sure how to do it.

Basically we have an interface ProcessData

public interface ProcessData {
  public setDelegate(Object delegate);
  public Object getDelegate();
  //I am sure these delegate methods can use generics somehow
}

Now we have a class ProcessDataGeneric that implements ProcessData

public class ProcessDataGeneric implements ProcessData {
  private Object delegate;

  public ProcessDataGeneric(Object delegate) {
    this.delegate = delegate;
  }
}

Now a new interface that retrieves the ProcessData

interface ProcessDataWrapper {
  public ProcessData unwrap();
}

Now a common abstract class that implements the wrapper so ProcessData can be retrieved

@XmlSeeAlso( { ProcessDataMotorferdsel.class,ProcessDataTilskudd.class })
public abstract class ProcessDataCommon implements ProcessDataWrapper {
  protected ProcessData unwrapped;

  public ProcessData unwrap() {
    return unwrapped;
  }
}

Now the implementation

public class ProcessDataMotorferdsel extends ProcessDataCommon {

  public ProcessDataMotorferdsel() {
    unwrapped = new ProcessDataGeneric(this);
  }
}

similarly

public class ProcessDataTilskudd extends ProcessDataCommon {

  public ProcessDataTilskudd() {
    unwrapped = new ProcessDataGeneric(this);
  }
}

Now when I use these classes, I always need to do instanceof

ProcessDataCommon pdc = null;
if(processData.getDelegate() instanceof ProcessDataMotorferdsel) {
   pdc = (ProcessDataMotorferdsel) processData.getDelegate();
} else if(processData.getDelegate() instanceof ProcessDataTilskudd) {
   pdc = (ProcessDataTilskudd) processData.getDelegate();
}

I know there is a better way to do this, but I have no idea how I can utilize Generics and the Visitor Pattern. Any help is GREATLY appreciated.

UPDATE

I want to add that these classes are just snippets of a much larger implementation. The ProcessData and ProcessDataGeneric is something that is outside of the delegates (ProcessDataMotorferdsel and so on). The delegates all extends ProcessDataCommon.

I can agree that a refactoring is probably best to do, but this is production code that is 2 years old, and it is costly to refactor (time,testing, etc). However, I am willing to do it.

UPDATE #2

I have tried to start the Generic process, however I am getting compile error. This is how it looks now.

public interface ProcessData<T extends ProcessDataCommon> {
  T getDelegate();
  setDelegate(T delegate);
}

public class ProcessDataGeneric<T extends ProcessDataCommon> implements ProcessData<T> {
  private T delegate;
  //Getter & setter
  public ProcessDataGeneric(T delegate) {
    this.delegate = delegate;
  }
}

public class ProcessDataMotorferdsel extends ProcessDataCommon {
  public ProcessDataMotorferdsel() {
    unwrapped = new ProcessDataGeneric<ProcessDataMotorferdsel>(this);
  }
}

I get compile error on line: unwrapped = new ProcessDataGeneric<ProcessDataMotorferdsel>(this); Saying

[javac] ProcessDataMotorferdsel.java:52: incompatible types [javac] found : ProcessDataGeneric<ProcessDataMotorferdsel> [javac] required: ProcessData<ProcessDataCommon> [javac]

I cannot make heads or tails of that error message. The ProcessDataMotorferdsel class extends ProcessDataCommon, so IMO it should work.

A: 

Maybe I'm missing something too, but

ProcessDataCommon pdc = null;
if(processData.getDelegate() instanceof ProcessDataCommon) {
   pdc = (ProcessDataCommon) processData.getDelegate();
}

should be equivalent..? As you mentioned, the delegate is always ProcessDataCommon type.

If ProcessData#getDelegate() would then return a ProcessDataCommon, you could eliminate the remaining instanceof check too.

Andreas_D
Yes. But I just wanted to show that I have to cast all the time. Actually what I am casting to is the implementation class (`ProcessDataMotorferdsel`) and not `ProcessDataCommon`
Shervin
Yes, but in your example, pdc is a ProcessDataCommon variable, so there's no advantage in casting to a subclass.
Andreas_D
+1  A: 

This answer is probably over-simplistic, but I think that nearly all of the code in your question would be eliminated by an ideal solution. The problem for people trying to answer the question is that the real problem that the code is trying to solve isn't clear from what is left over.

A common approach to refactoring away instanceof is to use sub-classes in conjunction with a "tell not ask" style of interface. There is no need to ask ProcessDataGeneric for its delegate if you can tell ProcessDataGeneric to do the whole task for you, like this:

public interface ProcessData {
    public <T> T process(Data data);
}

public class ProcessDataGeneric implements ProcessData {
    private ProcessData delegate;

    public ProcessDataGeneric(ProcessData delegate) {
        this.delegate = delegate;
    }

    public <T> T process(Data data) {
        return delegate.process(data);
}

I'm not even sure that you really need ProcessDataGeneric, since all it does is hold the real ProcessData sub-class:

public class ProcessDataMotorferdsel implements ProcessData {

    // Process the data the Motorferdsel way.
    public <T> T process(Data data) { ... }
}

public class ProcessDataTilskudd implements ProcessData {

    // Process the data the Tilskudd way.
    public <T> T process(Data data) { ... }
}

... and then you can use the sub-classes like this:

ProcessData processor = new ProcessDataMotorferdsel();
Result      result    = processor.process(data);

... without having to worry about delegates and what type they are.

It is often better to use a factory class instead of a constructor to obtain the sub-class instance, particularly if the correct sub-class needs to be calculated.

richj
What is the `Data` object here supposed to be?Yes I agree that my code uses lots of different classes which may not be necessary. Unfortunately that is how it is right now, and this is a 2 year old code that runs in production, so removing some of the classes causes lots of refactoring and testing
Shervin
I've had to guess at what the classes do, because the code in the question only shows the mechanics of wrapping and delegation. There isn't any information as to what the code actually does. Therefore I took a leap of faith and guessed that a ProcessData interface might process some data and return a result. The generic return type should allow the compiler to use type inference to match the way that the method is called/implemented. Java generics can be quite difficult to use, so a simple Object return might work better, depending on what you really want to do.
richj
Implementing a solution without modifying the existing code adds an extra dimension to the problem, but I thought the purpose of the question was to make it easier to use and maintain the code by eliminating the instanceof tests in the client code?
richj
@richj: Yes I am willing to refactor and change the code. The reason why I haven't included more of the implementation is because I don't see the relevance as to the question. The objects/classes I have shown are the part that is relevant to be able to generify and add Visitor pattern. (At least IMO). Also, I want to avoid using `Object` because then I will need to manually cast.Should maybe in this example `Data` be the `ProcessDataCommon` ?
Shervin
It might have been better if I had used a different name - perhaps DataProcessor instead of ProcessData because in my example the DataProcessor interface contains different methods and serves a different purpose. In my example the ProcessData performs an application related data processing operation, whereas in your question DataProcess deals with getting and setting a delegate.
richj
Incidentally, the main purpose of a Visitor is to allow a new operation to be defined for a class without changing the definition of the class. I am using something closer to a Strategy pattern to do the same thing. If you don't need double-dispatch or to apply the operation to each node in a complex data structure then Strategy is simpler and has fewer disadvantages compared to Visitor.
richj
A: 

You don't have to cast if your object already extends the desired target class. I mean, you can do this:

public interface ProcessData {
    public void setDelegate(ProcessDataCommon delegate);
    public ProcessDataCommon getDelegate();
}

and this:

public class ProcessDataGeneric implements ProcessData {
    private ProcessDataCommon delegate;
    public ProcessDataGeneric(ProcessDataCommon delegate) {
        this.delegate = delegate;
    }
    @Override
    public ProcessDataCommon getDelegate() {
        return delegate;
    }
    @Override
    public void setDelegate(ProcessDataCommon delegate) {
        this.delegate = delegate;
    }
}

And your instanceof comb simplifies to:

ProcessDataCommon pdc = processData.getDelegate();
wallenborn
A: 

I made it work.

Look at UPDATE #2 and including this change:

public abstract class ProcessDataCommon<T extends ProcessDataCommon<?>> implements ProcessDataWrapper {
}

Made everything compile.

Shervin