带有泛型的 Java 工厂模式

标签 java generics inheritance

我想要我的 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

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 参数,则会收到错误:

Type mismatch: cannot convert from BaseballUserInterface<Baseball> to 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);
    }

}

最佳答案

这是错误的设计模式。与其使用一种通用方法和一个 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 的默认实现。 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);
    }
}

为了使事情更简洁,最好将 getBallUserInterface 包设为私有(private),并在 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)。

其次,您需要在 Ball 上定义一个抽象的 accept 方法,该方法接受 VisitorBall 的每个具体实现也必须实现此方法才能使访问者模式正常工作。实现看起来完全一样,但类型系统确保分派(dispatch)适当的方法。

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

关于带有泛型的 Java 工厂模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12628251/

相关文章:

java - Java 中的参数化良构和捕获转换

c# - 如何从父实例实例化子实例

java - 有没有更有效的方法来计算每个连续字母在字符串中出现的次数? (java)

java - 如何防止 "Provided document path must not be null."Firestore?

java - Java 中的正则表达式。意外行为

java - 如何选择 JList 中每个单元格包含包含 JTextArea 的 JPanel 的行

generics - int64 不支持 LanguagePrimitives.DivideByInt?

c# - 是否可以通用地实现这个接口(interface),以便它只能传递一个类型参数?

c# - 如何在派生类时更改常量或静态变量的值?

java - 继承返回空值