При создании конструктора с суперклассом родитель не может вернуть экземпляр дочернего класса [дубликат]
На этот вопрос уже есть ответ здесь:
- подкласс класса Java Builder 10 ответов
Если я использую шаблон builder для настройки новых объектов, я могу иметь два класса, такие как Game
и HockeyGame
(показано ниже). Когда я хочу создать новый HockeyGame
, я получаю его конструктор и начинаю вызывать методы для настройки объекта по мере необходимости.
Проблема, с которой я сталкиваюсь, показана в основной функции. Как только я вызываю один метод из суперкласса, он возвращается как intance Game.Builder
, и я больше не могу вызывать какой-либо метод из дочернего класса.
Как лучше всего справиться с этим?
Главная.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();
}
}
Игра.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);
}
}
}
Хоккейная игра.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);
}
}
}
Спасибо.
2 ответа:
Вам нужно использовать трюк
getThis()
, который широко распространен в очень беглом коде API.Сначала вам нужно сделать свой
Game.Builder
родовым в себе :public static class Builder<B extends Builder<B>>
Затем вы добавляете метод
getThis()
:public B getThis() { return (B) this; }
Теперь вы меняете сеттеры, чтобы вернуть
B
иreturn 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<?>
и скопировать переменную оттуда - это немного приводит код в порядок.
Фактический взлом, как вы, возможно, заметили, здесь:
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
, поэтому проблема должна быть очевидной.
Попробуйте что-нибудь вроде этого
Вы явно возвращаете его к объекту
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; }