使用超类创建构建器时,父级不能返回子类的实例

Jak*_*mas 3 java design-patterns builder

如果我使用构建器模式来配置新对象,我可能有两个类GameHockeyGame(如下所示).当我想创建一个新的时HockeyGame,我得到它的构建器并开始调用方法来根据需要配置对象.

我遇到的问题显示在main函数中.一旦我从超类中调用一个方法,它就作为一个intance返回Game.Builder,我不能再调用子类中的任何方法.

处理这个问题的最佳方法是什么?

Main.java

class Main {

    public static void main(String[] args){

        HockeyGame hg = new HockeyGame.Builder()
                .setScore(5)
                .setTimeLimit(3600)
//--------------------------------------------------------------------
                .setIceTemperature(-5) // Error! Cannot call setIceTempurature() on
                                       // an instance of Game.Builder
//--------------------------------------------------------------------
                .build();


    }
}
Run Code Online (Sandbox Code Playgroud)

Game.java

public class Game{

    int score;
    int timeLimit;

    public Game(int score, int timeLimit) {
        this.score = score;
        this.timeLimit = timeLimit;
    }

    public static class Builder {

        int score;
        int timeLimit;

        public Builder setScore(int score) {
            this.score = score;
            return this;
        }

        public Builder setTimeLimit(int timeLimit) {
            this.timeLimit = timeLimit;
            return this;
        }

        public Game build() {
            return new Game(score, timeLimit);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

HockeyGame.java

public class HockeyGame extends Game {

    float iceTemperature;

    public HockeyGame(int score, int timeLimit, float iceTemperature) {
        super(score, timeLimit);
        this.iceTemperature = iceTemperature;
    }

    public static class Builder extends Game.Builder {

        float iceTemperature;

        public HockeyGame.Buidler setIceTemperature(float iceTemperature) {
            this.iceTemperature = iceTemperature;
            return this;
        }

        public HockeyGame build(){
            return new HockeyGame(score, timeLimit, iceTemperature);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

谢谢.

Bor*_*der 5

您需要使用getThis()流畅的API代码中流行的技巧.

首先,你需要让你的Game.Builder通用在自身:

public static class Builder<B extends Builder<B>>
Run Code Online (Sandbox Code Playgroud)

然后你添加一个getThis()方法:

public B getThis() {
    return (B) this;
}
Run Code Online (Sandbox Code Playgroud)

现在你改变你的setter方法返回一个Breturn getThis()而不是this:

public B setTimeLimit(int timeLimit) {
    //...
    return getThis();
}
Run Code Online (Sandbox Code Playgroud)

您的扩展类本身也需要是通用的:

public static class Builder<B extends Builder<B>> extends Game.Builder<B>
Run Code Online (Sandbox Code Playgroud)

现在您可以使用代码,它将"记住"预期的类型:

HockeyGame hockeyGame = new HockeyGame.Builder<>().setScore(10)
        .setTimeLimit(20)
        .setIceTemperature(-1)
        .build();
Run Code Online (Sandbox Code Playgroud)

最终的代码如下所示:

public class Game {

    private final int score;
    private final int timeLimit;

    private Game(final Builder<?> builder) {
        this.score = builder.score;
        this.timeLimit = builder.timeLimit;
    }

    public static class Builder<B extends Builder<B>> {

        private int score;
        private int timeLimit;

        public B setScore(int score) {
            this.score = score;
            return getThis();
        }

        public B setTimeLimit(int timeLimit) {
            this.timeLimit = timeLimit;
            return getThis();
        }

        protected B getThis() {
            return (B) this;
        }

        public Game build() {
            return new Game(this);
        }
    }
}

public class HockeyGame extends Game {

    private final float iceTemperature;

    private HockeyGame(final Builder<?> builder) {
        super(builder);
        this.iceTemperature = builder.iceTemperature;
    }

    public static class Builder<B extends Builder<B>> extends Game.Builder<B> {

        private float iceTemperature;

        public B setIceTemperature(float iceTemperature) {
            this.iceTemperature = iceTemperature;
            return getThis();
        }

        @Override
        public HockeyGame build() {
            return new HockeyGame(this);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:我已经制作了字段private final和主要类型构造函数 - 这迫使人们使用Builder.此外,构造函数可以Builder<?>从那里获取并复制变量 - 这会稍微整理一下代码.


你可能已经注意到,实际的黑客攻击是:

public B getThis() {
    return (B) this;
}
Run Code Online (Sandbox Code Playgroud)

在这里,我们强制Builder转换为其泛型类型 - 这允许我们根据所使用的特定实例更改方法的返回类型.问题是,如果您声明一个Builder类似以下内容的新内容:

public static class FootballGame extends Game {

    private FootballGame(final Builder<?> builder) {
        super(builder);
    }

    public static class Builder<B extends HockeyGame.Builder<B>> extends Game.Builder<B> {

        float iceTemperature;

        @Override
        public FootballGame build() {
            return new FootballGame(this);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这会在运行时爆炸ClassCastException.但该setter方法将返回一个HockeyGame.Builder而不是FootballGame.Builder这样的问题应该是显而易见的.