【问题标题】:When creating a builder with a superclass, parent cannot return instance of child class [duplicate]创建具有超类的构建器时,父类无法返回子类的实例[重复]
【发布时间】:2016-03-12 20:38:15
【问题描述】:

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

我遇到的问题显示在 main 函数中。一旦我从超类调用一个方法,它就会以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();


    }
}

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

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

谢谢。

【问题讨论】:

    标签: java design-patterns builder


    【解决方案1】:

    您需要使用在很多流畅的 API 代码中流行的 getThis() 技巧。

    首先你需要让你的Game.Builder本身中具有通用性:

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

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

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

    现在您将设置器更改为返回 Breturn getThis() 而不是 this

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

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

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

    现在您可以使用代码,它会“记住”预期的类型:

    HockeyGame hockeyGame = new HockeyGame.Builder<>().setScore(10)
            .setTimeLimit(20)
            .setIceTemperature(-1)
            .build();
    

    这个最终代码看起来像:

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

    N.B:我已经创建了字段 private final 以及主要类型构造函数 - 这迫使人们使用 Builder。此外,构造函数可以采用 Builder&lt;?&gt; 并从那里复制变量 - 这会稍微整理代码。


    您可能已经注意到,实际的 hack 在这里:

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

    在这里,我们强制将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);
            }
        }
    }
    

    这将在运行时以ClassCastException 爆炸。但是setter 方法将返回HockeyGame.Builder 而不是FootballGame.Builder,所以问题应该很明显。

    【讨论】:

    • 谢谢。这应该有效。我在 SO 上看过类似的答案,但认为使用 getThis() 方法似乎有点臭。我想它提供的功能是可以接受的气味。
    • 鲍里斯太棒了
    【解决方案2】:

    试试这样的

    您明确地将其转换回 HockeyGame.Builder 对象并在其上使用其自己的方法。

    您遇到的问题是 setTimeLimit 返回一个 Builder 对象(母类),因此您不能在其上使用子方法。

    HockeyGame hg = ((HockeyGame.Builder)(new HockeyGame.Builder().setScore(5)
                                                                  .setTimeLimit(3600)))
                                                                  .setIceTemperature(-5)
                                                                  .build();
    

    另外,setIceTemparature 应该返回一个HockeyGame.Builder 对象,以便能够对其进行build

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

    【讨论】:

    • 拥有你想要的东西是最简单的方法,但通用构建器是更好的方法。
    • @ByeBye 当然可以,但这对我来说是最简单的方法。
    • 感谢您指出 type-o。固定的。不幸的是,我不认为像你展示的那样铸造是最好的方法。它肯定会像您编写的那样工作,但是如果我有更多的函数调用全部交错怎么办。我会放四五个单独的演员表吗?有点脏。
    • @JakeThomas 确实,我已经尽力了 :)
    • @ByeBye 这似乎是个不错的方法,但最终却被送进了精神病院……
    猜你喜欢
    • 2014-11-29
    • 2021-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多