我希望我的BallUserInterfaceFactory
返回具有正确泛型类型的用户界面的实例。我被困在下面的例子中得到错误:
绑定不匹配:泛型方法getBaseballUserInterface(BASEBALL) 类型BallUserInterfaceFactory不适用于参数 (球)。推断类型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);
}
}
据我所知,它无法保证BALL是一个棒球,因此getBaseballUserInterface方法调用中存在参数类型不匹配。
如果我在getBaseballUserInterface方法调用中转换ball参数,那么我得到错误:
类型不匹配:无法从
BaseballUserInterface<Baseball>
转换 到BallUserInterface<BALL>
因为它不能保证我返回的是同一类型的BALL。
我的问题是,处理这种情况的策略是什么?
(为了完整性,这里是示例中所需的其他类)
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);
}
}
答案 0 :(得分:21)
这是一种错误的设计模式。您应该使用重载,而不是使用一个通用方法和if梯形图。重载消除了对if梯的需要,并且编译器可以确保调用正确的方法而不必等到运行时。
例如
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);
}
}
这样,如果您的代码无法为相应的球创建BallUserInterface
,您还可以获得编译时错误的额外好处。
要避免使用if梯形图,您可以使用称为双重调度的技术。本质上,我们使用实例知道它属于哪个类的事实,并为我们调用适当的工厂方法。为此,Ball
需要有一个返回相应BallInterface
的方法。
您可以将方法设为抽象,也可以提供抛出异常或返回null的默认实现。球和棒球应该看起来像:
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);
}
}
为了让事情变得更整洁,最好将getBallUserInterface
个软件包设为私有,并在BallUserInterfaceFactory
中提供通用的getter。然后,工厂可以管理其他检查,例如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();
}
}
正如评论中指出的,上述问题之一是它需要Ball
类知道UI,这是非常不受欢迎的。但是,您可以使用访问者模式,这使您可以使用双重调度,但也可以分离各种Ball
类和UI。
首先,必要的访客类和工厂功能:
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
}
您会注意到访问者和工厂功能必须使用通配符。这对于类型安全是必要的。由于您不知道传递了什么类型的球,因此该方法无法确定返回的UI(除了它是一个球UI)。
其次,您需要在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);
}
}
最后,一些代码可以将所有这些放在一起:
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
答案 1 :(得分:3)
这是一个非常好的问题。
你可以强奸
return (BallUserInterface<BALL>)getBaseballUserInterface((Baseball)ball);
答案在理论上是有缺陷的,因为我们强制BASEBALL=Baseball
。
它因擦除而起作用。实际上取决于擦除。
我希望有一个更好的答案是具体化安全。
答案 2 :(得分:0)
public class BaseballUserInterface extends BallUserInterface<Baseball> {
public BaseballUserInterface(Baseball ball) {
super(ball);
}
}
作为工厂方法的结果,您正在使用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);
}
}
如果客户对球的类型感兴趣,你应该提供一个工厂方法,混凝土球作为参数:
public static BaseballUserInterface getUserInterface(Baseball ball){
return new BaseballUserInterface(ball);
}