带有泛型的 Java 工厂模式
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12628251/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java Factory Pattern With Generics
提问by FuryComputers
I would like my BallUserInterfaceFactory
to return an instance of a user interface that has the proper generic type. I am stuck in the example below getting the error:
我希望BallUserInterfaceFactory
返回一个具有正确泛型类型的用户界面实例。我被困在下面的示例中,出现错误:
Bound mismatch: The generic method getBaseballUserInterface(BASEBALL) of type BallUserInterfaceFactory is not applicable for the arguments (BALL). The inferred type BALL is not a valid substitute for the bounded parameter
绑定不匹配:BallUserInterfaceFactory 类型的通用方法 getBaseballUserInterface(BASEBALL) 不适用于参数 (BALL)。推断类型 BALL 不是有界参数的有效替代品
public class BallUserInterfaceFactory {
public static <BALL extends Ball> BallUserInterface<BALL> getUserInterface(BALL ball) {
if(ball instanceof Baseball){
return getBaseballUserInterface(ball);
}
//Other ball types go here
//Unable to create a UI for ball
return null;
}
private static <BASEBALL extends Baseball> BaseballUserInterface<BASEBALL> getBaseballUserInterface(BASEBALL ball){
return new BaseballUserInterface<BASEBALL>(ball);
}
}
I understand that it cannot guarantee that BALL is a Baseball, and so there is a parameter type mismatch on the getBaseballUserInterface method call.
我知道它不能保证 BALL 是棒球,因此 getBaseballUserInterface 方法调用存在参数类型不匹配。
If I cast the ball parameter in the getBaseballUserInterface method call, then I get the error:
如果我在 getBaseballUserInterface 方法调用中投射 ball 参数,则会收到错误消息:
Type mismatch: cannot convert from
BaseballUserInterface<Baseball>
toBallUserInterface<BALL>
类型不匹配:无法转换
BaseballUserInterface<Baseball>
为BallUserInterface<BALL>
Because it can't guarantee that what I am returning is the same type of BALL.
因为它不能保证我返回的是同类型的 BALL。
My question is, what is the strategy for dealing with this situation?
我的问题是,处理这种情况的策略是什么?
(For completeness, here are the other classes required in the example)
(为了完整起见,这里是示例中所需的其他类)
public class Ball {
}
public class Baseball extends Ball {
}
public class BallUserInterface <BALL extends Ball> {
private BALL ball;
public BallUserInterface(BALL ball){
this.ball = ball;
}
}
public class BaseballUserInterface<BASEBALL extends Baseball> extends BallUserInterface<BASEBALL>{
public BaseballUserInterface(BASEBALL ball) {
super(ball);
}
}
采纳答案by irreputable
This is a VERY GOOD question.
这个问题问得好。
You could cast brutely
你可以粗暴地施放
return (BallUserInterface<BALL>)getBaseballUserInterface((Baseball)ball);
The answer is theoretically flawed, since we force BASEBALL=Baseball
.
答案在理论上是有缺陷的,因为我们强制BASEBALL=Baseball
.
It works due to erasure. Actually it dependson erasure.
它由于擦除而起作用。实际上这取决于擦除。
I hope there is a better answer that is reification safe.
我希望有一个更好的答案是reification safe。
回答by Dunes
This is a wrong design pattern. Rather than using one generic method and an if ladder, you should instead use overloading. Overloading eliminates the need for the if ladder and the compiler can make sure the correct method is invoked rather than having to wait till runtime.
这是错误的设计模式。与其使用一种通用方法和一个 if 阶梯,不如使用重载。重载消除了对 if 梯形图的需要,编译器可以确保调用正确的方法,而不必等到运行时。
eg.
例如。
public class BallUserInterfaceFactory {
public static BallUserInterface<Baseball> getUserInterface(
Baseball ball) {
return new BallUserInterface<Baseball>(ball);
}
public static BallUserInterface<Football> getUserInterface(
Football ball) {
return new BallUserInterface<Football>(ball);
}
}
This way you also get the added benefit of compile time errors if your code cannot create a BallUserInterface
for the appropriate ball.
这样,如果您的代码无法BallUserInterface
为适当的球创建一个,您还可以获得编译时错误的额外好处。
To avoid the if ladder you can use a technique known as double dispatch. In essence, we use the fact that the instance knows what class it belongs to and calls the appropriate factory method for us. For this to work Ball
needs to have a method that returns the appropriate BallInterface
.
为了避免 if 阶梯,您可以使用一种称为双重调度的技术。本质上,我们利用实例知道它属于哪个类并为我们调用适当的工厂方法这一事实。为此,Ball
需要有一个方法可以返回适当的BallInterface
.
You can either make the method abstract or provide a default implementation that throws an exception or returns null. Ball and Baseball should now look something like:
您可以使该方法抽象或提供一个默认实现,该实现会引发异常或返回 null。Ball 和 Baseball 现在应该类似于:
public abstract class Ball<T extends Ball<T>> {
abstract BallUserInterface<T> getBallUserInterface();
}
.
.
public class Baseball extends Ball<Baseball> {
@Override
BallUserInterface<Baseball> getBallUserInterface() {
return BallUserInterfaceFactory.getUserInterface(this);
}
}
To make things a little neater, it's better to make getBallUserInterface
package private and provide a generic getter in BallUserInterfaceFactory
. The factory can then manage additional checks like for null and any thrown exceptions. eg.
为了使事情更整洁,最好将getBallUserInterface
包设为私有并在BallUserInterfaceFactory
. 然后工厂可以管理额外的检查,比如 null 和任何抛出的异常。例如。
public class BallUserInterfaceFactory {
public static BallUserInterface<Baseball> getUserInterface(
Baseball ball) {
return new BallUserInterface<Baseball>(ball);
}
public static <T extends Ball<T>> BallUserInterface<T> getUserInterface(
T ball) {
return ball.getBallUserInterface();
}
}
The Visitor Pattern
访问者模式
As pointed out in the comments, one problem of the above is it requires the Ball
classes to have knowledge of the UI, which is highly undesirable. You can, however, use the visitor pattern, which enables you to use double dispatch, but also decouples the various Ball
classes and the UI.
正如评论中所指出的,上面的一个问题是它要求Ball
类具有 UI 的知识,这是非常不可取的。但是,您可以使用访问者模式,它使您能够使用双重调度,而且还可以分离各种Ball
类和 UI。
First, the necessary visitor classes, and factory functions:
首先,必要的访问者类和工厂函数:
public interface Visitor<T> {
public T visit(Baseball ball);
public T visit(Football ball);
}
public class BallUserInterfaceVisitor implements Visitor<BallUserInterface<? extends Ball>> {
@Override
public BallUserInterface<Baseball> visit(Baseball ball) {
// Since we now know the ball type, we can call the appropriate factory function
return BallUserInterfaceFactory.getUserInterface(ball);
}
@Override
public BallUserInterface<Football> visit(Football ball) {
return BallUserInterfaceFactory.getUserInterface(ball);
}
}
public class BallUserInterfaceFactory {
public static BallUserInterface<? extends Ball> getUserInterface(Ball ball) {
return ball.accept(new BallUserInterfaceVisitor());
}
// other factory functions for when concrete ball type is known
}
You'll note that the visitor and the factory function have to use wildcards. This is necessary for type safety. Since you don't know what type of ball has been passed, the method cannot be sure of what UI is being returned (other than it is a ball UI).
您会注意到访问者和工厂函数必须使用通配符。这是类型安全所必需的。由于您不知道传递的是什么类型的球,因此该方法无法确定返回的是什么 UI(除了它是球 UI)。
Secondly, you need to define an abstract accept
method on Ball
that accepts a Visitor
. Each concrete implementation of Ball
must also implement this method for the visitor pattern to work correctly. The implementation looks exactly the same, but the type system ensures dispatch of the appropriate methods.
其次,您需要定义一个接受 a的抽象accept
方法。的每个具体实现也必须实现这个方法,访问者模式才能正常工作。实现看起来完全一样,但类型系统确保了适当的方法的分派。Ball
Visitor
Ball
public interface Ball {
public <T> T accept(Visitor<T> visitor);
}
public class Baseball implements Ball {
@Override
public <T> T accept(Visitor<T> visitor) {
return visitor.visit(this);
}
}
Finally, a bit of code that can put all this together:
最后,一些可以将所有这些组合在一起的代码:
Ball baseball = new Baseball();
Ball football = new Football();
List<BallUserInterface<? extends Ball>> uiList = new ArrayList<>();
uiList.add(BallUserInterfaceFactory.getUserInterface(baseball));
uiList.add(BallUserInterfaceFactory.getUserInterface(football));
for (BallUserInterface<? extends Ball> ui : uiList) {
System.out.println(ui);
}
// Outputs:
// ui.BaseballUserInterface@37e247e2
// ui.FootballUserInterface@1f2f0ce9
回答by ollins
public class BaseballUserInterface extends BallUserInterface<Baseball> {
public BaseballUserInterface(Baseball ball) {
super(ball);
}
}
You are using the BallUserInterface as a result of the factory method. So, it can be hidden which concrete ball is used:
您正在使用 BallUserInterface 作为工厂方法的结果。因此,可以隐藏使用哪个混凝土球:
public class BallUserInterfaceFactory {
public static BallUserInterface<?> getUserInterface(Ball ball) {
if(ball instanceof Baseball){
return getBaseballUserInterface((Baseball)ball);
}
return null;
}
private static BaseballUserInterface getBaseballUserInterface(Baseball ball){
return new BaseballUserInterface(ball);
}
}
If the client is interested in the type of the ball you should offer a factory method with the concrete ball as parameter:
如果客户对球的类型感兴趣,您应该提供一个以混凝土球为参数的工厂方法:
public static BaseballUserInterface getUserInterface(Baseball ball){
return new BaseballUserInterface(ball);
}