3

I have a Data class with several sub-class such as JSONData, XMLData, IntegerData. The task is to process different types of incoming data. So based on program to an interface, not an implementation, I created the following interface with generic type for compile-time type checking:

interface DataProcessor<T extends Data> {
    void process(T data);
}

There are several implemenations based on this interface:

* `class JSONDataProcessor implements DataProcessor<JSONData>`
* `class XMLDataProcessor implements DataProcessor<XMLData>`
* `class IntegerDataProcessor implements DataProcessor<IntegerData>`

The rest of the work is to make a simple factory for creating the corresponding DataProcessor instance. So I made the following simple factory, or say it is in fact just a processor mapper as the concrete processor can be cached as static variables inside the ProcessorFactory:

public class ProcessorFactory {
    public static DataProcessor<?> create() {
          //logic of return an instance
    }
}

The design above has a problem - the process method on the returned instance of DataProcessor cannot be called directly:

Data data = jsonData;
ProcessorFactory.create().process(data);

Question: the code above has a compilation error due to the compile-time typing checking as the data has to be the concrete sub-class of Data, how to resolve this? Or is the design per se. bad? if so what would be a better design?

Rui
  • 3,454
  • 6
  • 37
  • 70

2 Answers2

1

While design patterns are cool and all, the compilation error you reported in your question, was not caused by the lack of double dispatch.

You get the compilation error because by declaring, for example, this: JSONDataProcessor implements DataProcessor<JSONData>{...} you've declared this method: void process(JSONData data).

You are probably assuming that <T extends Data> means you can pass an object instance with the static type Data into void process(JSONData data) because, after all, Data extends Data. Except that's not how Java works.

One way to look at the cause of your compilation error is to consider a method declaration like: public static void main(String arg){...}. Even though String extends Object, it is illegal to pass a reference with static type Object into a method declared as main(String).

You would get the same compilation error you got with your DataProcessor if you tried to call that method as main(new Object()). It would be overkill to correct a mistake that you made, by introducing an unnecessary design pattern. In both your case and in the case of main(String), the simplest correction is to pass in the type that the method is declared to take.

…how to resolve this?…

The simplest solution, in my opinion, is to use your methods the way you declared them originally. If yours is implemented in a similar way that mine is, then I've confirmed that this works…

...
JSONData data = new JSONData( ... );
ProcessorFactory.create().process(data);
...

This also works in my demo (no design patterns required)…

DataProcessor< Data< ? > > dProc = DataProcessor.Factory.create( );
Data<String> data = new JSONData( ... );
dProc.process( data );

…is the design per se. bad?…

To call a design „good“ or „bad“ is subjective. It's more objective to ask yourself: Is the design correct? Does it correctly do what you intend it to do? If it does what you intend it to do, then it's correct. If it doesn't, then back to the white drawing board.

Another design option you have is to decide not to use Generics at all — nor design patterns. Something way simpler might be all you need.

You mentioned: „program to an interface“. Maybe all your design needs is plain old subtype polymorphism in the form of old-fashioned interfaces. Generics might not be the best design choice for what you want to do.

deduper
  • 1,944
  • 9
  • 22
  • thanks really so much for the answer. I indeed learnt a lot from you answer :) as the first answer is marked as correct, I vote for your answer anyway – Rui Aug 12 '20 at 13:40
  • Thanks @Rui! — „*...I indeed learnt a lot from you answer :)*“ — Hey I'm glad it helped you :) — „*...the first answer is marked as correct...*“ — Do you know about: „[*Accepting Answers: How does it work?*](https://meta.stackoverflow.com/a/251399/4465539)“? In particular, did you know about the part that says: „*You may change which answer is accepted, or simply un-accept the answer, at any time*“? I'm also curious to know how close [*the* _`DataProcessor.Factory.create(Class

    )`_ *implementation in my demo*](https://www.browxy.com#USER_306909) is to your original design intentions? TIA :)

    – deduper Aug 12 '20 at 15:01
  • I checked yours as the correct answer. But @AvishekBhattacharya 's answer indeed valuable as he mentioned about the single dispatching of Java, which is great – Rui Aug 12 '20 at 15:05
  • @Avishek Bhattacharya sorry that i changed the correct answer to deduper, but i indeed appreciate your answer very much also – Rui Aug 12 '20 at 15:07
  • Thank you @Rui! I love design patterns as much as anybody. ***BUT*** if you care about making „*good*“ design choices then you have to resist the potentially fatal allure of [*design pattern overkill*](https://www.bing.com/search?q=overuse+of+design+patterns). Java [*Language Architect, Brian Goetz*](https://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html#the-visitor-pattern) warns of the Visitor pattern's „*verbosity, intrusiveness, or restrictions*“. [*Static vs. Dynamic Type*](https://inst.eecs.berkeley.edu/~cs61bl/su15/materials/guides/static-dynamic.pdf) is another fun favorite. – deduper Aug 12 '20 at 16:37
0

This is the classic problem with Java as it doesn't support double dispatching. People have circumvented the problem using visitor pattern. In your case, you can possibly expose a visit function inside the Data class that accepts the DataProcessor and run the process method of it. Essentially, reverse the things.

Something like this

interface Data {

  ....
   void visit(DataProcessor processor);
}

Data d = JsonData;
d.visit(jsonDataProcessor processor);

And the visit function for JsonData looks like

void visit(JsonDataProcessor processor) {

    processor.process(this);
}
Avishek Bhattacharya
  • 6,534
  • 3
  • 34
  • 53
  • Thanks really so much. This is exactly what I would like to know :) shame on me that I know the visitor pattern, but really did not get the essence :) – Rui Aug 11 '20 at 13:23
  • AvishekBhattacharya? @Rui's code „*...has a compilation error due to the compile-time typing checking...*“ — Which was a result of the _`ProcessorFactory.create().process(data)`_ line of code in the question. The specific compilation error would have been: _`incompatible types: Data cannot be converted to capture#1 of ?`_. Your answer to the report of a *compilation error* is: „*...This is the classic problem with Java as it doesn't support double dispatching...*“ — Are you saying Java's lack of double dispatch causes capture conversion-related compilation errors? Can you explain why that is? – deduper Aug 12 '20 at 00:39