带有泛型的 Java 工厂模式

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12628251/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 09:37:37  来源:igfitidea点击:

Java Factory Pattern With Generics

javagenericsinheritance

提问by FuryComputers

I would like my BallUserInterfaceFactoryto return an instance of a user interface that has the proper generic type. I am stuck in the example below getting the error:

我希望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

绑定不匹配: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);
    }
}

I understand that it cannot guarantee that BALL is a Baseball, and so there is a parameter type mismatch on the getBaseballUserInterface method call.

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

If I cast the ball parameter in the getBaseballUserInterface method call, then I get the error:

如果我在 getBaseballUserInterface 方法调用中投射 ball 参数,则会收到错误消息:

Type mismatch: cannot convert from BaseballUserInterface<Baseball>to BallUserInterface<BALL>

类型不匹配:无法转换BaseballUserInterface<Baseball>BallUserInterface<BALL>

Because it can't guarantee that what I am returning is the same type of BALL.

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

My question is, what is the strategy for dealing with this situation?

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

(For completeness, here are the other classes required in the example)

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

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);
    }

}

采纳答案by irreputable

This is a VERY GOOD question.

这个问题问得好。

You could cast brutely

你可以粗暴地施放

    return (BallUserInterface<BALL>)getBaseballUserInterface((Baseball)ball);

The answer is theoretically flawed, since we force BASEBALL=Baseball.

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

It works due to erasure. Actually it dependson erasure.

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

I hope there is a better answer that is reification safe.

我希望有一个更好的答案是reification safe

回答by Dunes

This is a wrong design pattern. Rather than using one generic method and an if ladder, you should instead use overloading. Overloading eliminates the need for the if ladder and the compiler can make sure the correct method is invoked rather than having to wait till runtime.

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

eg.

例如。

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);
    }
}

This way you also get the added benefit of compile time errors if your code cannot create a BallUserInterfacefor the appropriate ball.

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



To avoid the if ladder you can use a technique known as double dispatch. In essence, we use the fact that the instance knows what class it belongs to and calls the appropriate factory method for us. For this to work Ballneeds to have a method that returns the appropriate BallInterface.

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

You can either make the method abstract or provide a default implementation that throws an exception or returns null. Ball and Baseball should now look something like:

您可以使该方法抽象或提供一个默认实现,该实现会引发异常或返回 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);
    }
}

To make things a little neater, it's better to make getBallUserInterfacepackage private and provide a generic getter in BallUserInterfaceFactory. The factory can then manage additional checks like for null and any thrown exceptions. eg.

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

The Visitor Pattern

访问者模式

As pointed out in the comments, one problem of the above is it requires the Ballclasses to have knowledge of the UI, which is highly undesirable. You can, however, use the visitor pattern, which enables you to use double dispatch, but also decouples the various Ballclasses and the UI.

正如评论中所指出的,上面的一个问题是它要求Ball类具有 UI 的知识,这是非常不可取的。但是,您可以使用访问者模式,它使您能够使用双重调度,而且还可以分离各种Ball类和 UI。

First, the necessary visitor classes, and factory functions:

首先,必要的访问者类和工厂函数:

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
}

You'll note that the visitor and the factory function have to use wildcards. This is necessary for type safety. Since you don't know what type of ball has been passed, the method cannot be sure of what UI is being returned (other than it is a ball UI).

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

Secondly, you need to define an abstract acceptmethod on Ballthat accepts a Visitor. Each concrete implementation of Ballmust also implement this method for the visitor pattern to work correctly. The implementation looks exactly the same, but the type system ensures dispatch of the appropriate methods.

其次,您需要定义一个接受 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);
    }
}

Finally, a bit of code that can put all this together:

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

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

回答by ollins

public class BaseballUserInterface extends BallUserInterface<Baseball> {

    public BaseballUserInterface(Baseball ball) {
        super(ball);
    }
}

You are using the BallUserInterface as a result of the factory method. So, it can be hidden which concrete ball is used:

您正在使用 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);
    }
}

If the client is interested in the type of the ball you should offer a factory method with the concrete ball as parameter:

如果客户对球的类型感兴趣,您应该提供一个以混凝土球为参数的工厂方法:

public static BaseballUserInterface getUserInterface(Baseball ball){
    return new BaseballUserInterface(ball);
}