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); } }
为了使事情变得更整洁,最好将getBallUserInterface
包getBallUserInterface
私有,并在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 extends Ball> 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 extends Ball> 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); }