如何在简单工厂中使用泛型解决这个困境

huangapple go评论78阅读模式
英文:

How to resolve this dilemma when using generics in a simple factory

问题

以下是您要求的代码部分的翻译:

我有一个名为`Data`的类它有几个子类`JSONData`、`XMLData`、`IntegerData`。任务是处理不同类型的输入数据因此基于针对接口编程而不是实现编程的原则我创建了以下带有泛型类型的接口以进行编译时类型检查

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

有几个实现基于这个接口

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

其余的工作是创建相应的`DataProcessor`实例的*简单工厂*因此我创建了以下简单工厂或者可以说它实际上只是一个处理器映射器因为具体的处理器可以缓存在ProcessorFactory内部的*静态变量*

public class ProcessorFactory {
    public static DataProcessor<?> create() {
          //返回一个实例的逻辑
    }
}

上述设计存在问题 - 不能直接调用返回的DataProcessor实例上的process方法:

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

问题:上述代码由于编译时类型检查而出现编译错误,因为数据必须是Data的具体子类。如何解决这个问题?或者这个设计本身有问题吗?如果有问题,有什么更好的设计方式?

如果要解决这个问题,您可以使用泛型工厂方法,如下所示:

public class ProcessorFactory {
    public static <T extends Data> DataProcessor<T> create(Class<T> dataType) {
        if (dataType == JSONData.class) {
            return (DataProcessor<T>) new JSONDataProcessor();
        } else if (dataType == XMLData.class) {
            return (DataProcessor<T>) new XMLDataProcessor();
        } else if (dataType == IntegerData.class) {
            return (DataProcessor<T>) new IntegerDataProcessor();
        } else {
            throw new IllegalArgumentException("Unsupported data type");
        }
    }
}

然后,您可以像这样使用工厂方法:

Data data = jsonData;
DataProcessor<JSONData> processor = ProcessorFactory.create(JSONData.class);
processor.process(data);

这种方式将根据输入的数据类型创建相应的处理器,并且不会出现编译错误。

英文:

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&lt;T extends Data&gt; {
    void process(T data);
}

There are several implemenations based on this interface:

* `class JSONDataProcessor implements DataProcessor&lt;JSONData&gt;`
* `class XMLDataProcessor implements DataProcessor&lt;XMLData&gt;`
* `class IntegerDataProcessor implements DataProcessor&lt;IntegerData&gt;`

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&lt;?&gt; 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?

答案1

得分: 1

设计模式虽然很酷,但你在问题中报告的编译错误并不是由于缺少双重分派引起的。

你之所以得到编译错误,是因为当你声明这样的代码:JSONDataProcessor implements DataProcessor&lt;JSONData&gt;{...} 时,你声明了这个方法:void process(JSONData data)

你可能认为 <T extends Data> 意味着你可以将静态类型为 Data 的对象实例传递给 void process(JSONData data),因为毕竟 Data extends Data。但这不是 Java 的工作方式。

解释你编译错误原因的一种方式是考虑一个方法声明,比如:public static void main(String arg){...}。尽管 String extends Object,但将静态类型为 Object 的引用传递给声明为 main(String) 的方法是非法的。

如果你尝试调用该方法为 main(new Object()),你会得到与你的 DataProcessor 相同的编译错误。通过引入不必要的设计模式来纠正你的错误是不划算的。在你的情况和 main(String) 的情况下,最简单的纠正方法是传递方法声明要接受的类型。

最简单的解决方案,在我看来,是按照你最初声明的方式使用你的方法。如果你的方法实现方式与我的类似,那么我确认这种方式是有效的:

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

这在我的演示中也有效(无需设计模式):

DataProcessor&lt; Data&lt; ? &gt; &gt; dProc = DataProcessor.Factory.create( );
Data&lt;String&gt; data = new JSONData( ... );
dProc.process( data );

关于设计本身是否好或坏,这是主观的。更客观的问题是要问自己:这个设计是否正确?它是否正确地执行了你打算做的事情?如果它做了你打算做的事情,那么它是正确的。如果没有,那么回到绘图板重新设计。

你还有另一种设计选择,可以决定根本不使用泛型,也不使用设计模式。可能只需更简单的东西。你提到了"按照接口编程"。也许你的设计只需要简单的旧式接口形式的子类型多态性。泛型可能不是实现你想要的功能的最佳设计选择。

英文:

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&lt;JSONData&gt;{...} you've declared this method: void process(JSONData data).

You are probably assuming that &lt;T extends Data&gt; 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.

> „&hellip;how to resolve this?&hellip;

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&hellip;

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

This also works in my demo (no design patterns required)&hellip;

DataProcessor&lt; Data&lt; ? &gt; &gt; dProc = DataProcessor.Factory.create( );
Data&lt;String&gt; data = new JSONData( ... );
dProc.process( data );

> „&hellip;is the design per se. bad?&hellip;

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 <strike>white</strike> 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.

答案2

得分: 0

This is the classic problem with Java as it doesn't support double dispatching. People have circumvented the problem using the visitor pattern. In your case, you can possibly expose a visit function inside the Data class that accepts the DataProcessor and runs 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);
}
英文:

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

huangapple
  • 本文由 发表于 2020年8月11日 15:53:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/63353844.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定