Java工厂模式与泛型

Fur*_*ers 22 java generics inheritance

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

绑定不匹配: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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我知道它不能保证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);
    }

}
Run Code Online (Sandbox Code Playgroud)

Dun*_*nes 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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


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

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

public abstract class Ball<T extends Ball<T>> {
    abstract BallUserInterface<T> getBallUserInterface();
}
Run Code Online (Sandbox Code Playgroud)

.

public class Baseball extends Ball<Baseball> {
    @Override
    BallUserInterface<Baseball> getBallUserInterface() {
        return BallUserInterfaceFactory.getUserInterface(this);
    }
}
Run Code Online (Sandbox Code Playgroud)

为了使事情变得更整洁,最好将getBallUserInterface软件包设为私有并提供通用的getter 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

访客模式

正如评论中指出的,上述问题之一是它需要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
}
Run Code Online (Sandbox Code Playgroud)

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

其次,你需要定义一个接受a 的抽象accept方法.每个具体实现还必须实现此方法,以使访问者模式正常工作.实现看起来完全相同,但类型系统确保调度适当的方法.BallVisitorBall

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

  • 对象不应该有关于其用户界面的任何信息,因此向对象添加UI返回方法不是正确的解决方案. (3认同)
  • 我添加了一些关于使用双重调度来避免梯子. (2认同)

irr*_*ble 3

这个问题问得好。

你可以粗暴地施放

    return (BallUserInterface<BALL>)getBaseballUserInterface((Baseball)ball);
Run Code Online (Sandbox Code Playgroud)

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

它是由于擦除而起作用的。实际上这取决于擦除。

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