Java工厂模式与generics

我希望我的BallUserInterfaceFactory返回具有正确generics类型的用户界面的实例。 我被困在下面的例子中得到错误:

绑定不匹配:BallUserInterfaceFactory类型的generics方法getBaseballUserInterface(BASEBALL)不适用于参数(BALL)。 推断类型BALL不是有界参数的有效替代

 public class BallUserInterfaceFactory { public static  BallUserInterface 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  BaseballUserInterface getBaseballUserInterface(BASEBALL ball){ return new BaseballUserInterface(ball); } } 

我知道它不能保证BALL是一个棒球,因此getBaseballUserInterface方法调用中存在参数类型不匹配。

如果我在getBaseballUserInterface方法调用中转换ball参数,那么我得到错误:

类型不匹配:无法从BaseballUserInterface转换为BallUserInterface

因为它不能保证我返回的是同一类型的BALL。

我的问题是,处理这种情况的策略是什么?

(为了完整性,这里是示例中所需的其他类)

 public class Ball { } public class Baseball extends Ball { } public class BallUserInterface  { private BALL ball; public BallUserInterface(BALL ball){ this.ball = ball; } } public class BaseballUserInterface extends BallUserInterface{ public BaseballUserInterface(BASEBALL ball) { super(ball); } } 

这个问题问得好。

你可以粗暴地施展

  return (BallUserInterface)getBaseballUserInterface((Baseball)ball); 

答案在理论上是有缺陷的,因为我们强迫BASEBALL=Baseball

它的工作原理是擦除。 实际上它取决于擦除。

我希望有一个更好的答案,即物化安全

这是一种错误的设计模式。 您应该使用重载,而不是使用一个通用方法和if梯形图。 重载消除了对if梯形图的需要,并且编译器可以确保调用正确的方法而不必等到运行时。

例如。

 public class BallUserInterfaceFactory { public static BallUserInterface getUserInterface( Baseball ball) { return new BallUserInterface(ball); } public static BallUserInterface getUserInterface( Football ball) { return new BallUserInterface(ball); } } 

这样,如果您的代码无法为相应的球创建BallUserInterface ,您还可以获得编译时错误的额外好处。


要避免使用if梯形图,您可以使用称为双重调度的技术。 本质上,我们使用实例知道它属于哪个类的事实,并为我们调用适当的工厂方法。 为此, Ball需要有一个返回相应BallInterface

您可以将方法设为抽象,也可以提供抛出exception或返回null的默认实现。 球和棒球应该看起来像:

 public abstract class Ball> { abstract BallUserInterface getBallUserInterface(); } 

 public class Baseball extends Ball { @Override BallUserInterface getBallUserInterface() { return BallUserInterfaceFactory.getUserInterface(this); } } 

为了使事情变得更整洁,最好将getBallUserInterfacegetBallUserInterface私有,并在BallUserInterfaceFactory提供通用的getter。 然后,工厂可以管理其他检查,例如null和任何抛出的exception。 例如。

 public class BallUserInterfaceFactory { public static BallUserInterface getUserInterface( Baseball ball) { return new BallUserInterface(ball); } public static > BallUserInterface getUserInterface( T ball) { return ball.getBallUserInterface(); } } 

访客模式

正如评论中指出的,上述问题之一是它需要Ball类具有UI的知识,这是非常不希望的。 但是,您可以使用访问者模式,这使您可以使用双重调度,但也可以分离各种Ball类和UI。

首先,必要的访客类和工厂function:

 public interface Visitor { public T visit(Baseball ball); public T visit(Football ball); } public class BallUserInterfaceVisitor implements Visitor> { @Override public BallUserInterface visit(Baseball ball) { // Since we now know the ball type, we can call the appropriate factory function return BallUserInterfaceFactory.getUserInterface(ball); } @Override public BallUserInterface visit(Football ball) { return BallUserInterfaceFactory.getUserInterface(ball); } } public class BallUserInterfaceFactory { public static BallUserInterface getUserInterface(Ball ball) { return ball.accept(new BallUserInterfaceVisitor()); } // other factory functions for when concrete ball type is known } 

您会注意到访问者和工厂function必须使用通配符。 这对于类型安全是必要的。 由于您不知道传递了什么类型的球,因此该方法无法确定返回的UI(除了它是一个球UI)。

其次,您需要在Ball上定义一个accept Visitor的抽象accept方法。 Ball每个具体实现也必须实现此方法,以使访问者模式正常工作。 实现看起来完全相同,但类型系统确保调度适当的方法。

 public interface Ball { public  T accept(Visitor visitor); } public class Baseball implements Ball { @Override public  T accept(Visitor visitor) { return visitor.visit(this); } } 

最后,一些代码可以将所有这些组合在一起:

 Ball baseball = new Baseball(); Ball football = new Football(); List> uiList = new ArrayList<>(); uiList.add(BallUserInterfaceFactory.getUserInterface(baseball)); uiList.add(BallUserInterfaceFactory.getUserInterface(football)); for (BallUserInterface ui : uiList) { System.out.println(ui); } // Outputs: // ui.BaseballUserInterface@37e247e2 // ui.FootballUserInterface@1f2f0ce9 
 public class BaseballUserInterface extends BallUserInterface { 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); }