Reputation: 91
Suppose I have a car builder with parameters such as model, color and speed:
public class Car {
private String model;
private int color;
private int speed;
public Car(String model, int color, int speed) {
this.model = model;
this.color = color;
this.speed = speed;
}
//..Getters
public static class Builder {
private String model;
private int color;
private int speed;
public Builder model(String model) {
this.model = model;
return this;
}
public Builder color(int color) {
this.color = color;
return this;
}
public Builder speed(int speed) {
this.speed = speed;
return this;
}
public Car build() {
return new Car(model, color, speed);
}
}
}
And I build cars like this:
Car car1 = new Car.Builder()
.model("Audi")
.color(Color.RED.getRGB())
.speed(200)
.build();
Car car2 = new Car.Builder()
.model("Audi")
.color(Color.RED.getRGB())
.speed(350)
.build();
Car car3 = new Car.Builder()
.model("Audi")
.color(Color.RED.getRGB())
.speed(175)
.build();
As you can see, I have parameters that have to be constantly duplicated. I would like to be able to build new cars based on some existing blanks like this:
Car car1 = new Car.Builder()
.initFrom(redAudi)
.speed(200)
.build();
Car car2 = new Car.Builder()
.initFrom(redAudi)
.speed(350)
.build();
Car car3 = new Car.Builder()
.initFrom(redAudi)
.speed(175)
.build();
}
Is there any template that provides for this?
Upvotes: 1
Views: 945
Reputation: 91
Thanks for the answers! I decided to go for @tquadrat's solution, but expand on it a bit. I decided to add an interface for the classes that initialize the builders.
Car.java
public class Car {
private final String model;
private final int color;
private final int speed;
public Car(String model, int color, int speed) {
this.model = model;
this.color = color;
this.speed = speed;
}
//..Getters
public static class Builder {
private String model;
private int color;
private int speed;
public Builder initFrom(@NotNull CarBuilderInitializer initializer) {
Builder builder = initializer.initBuilder(this);
this.model = builder.getModel();
this.color = builder.getColor();
this.speed = builder.getSpeed();
return this;
}
public Builder model(String model) {
this.model = model;
return this;
}
public Builder color(int color) {
this.color = color;
return this;
}
public Builder speed(int speed) {
this.speed = speed;
return this;
}
public Car build() {
return new Car(model, color, speed);
}
}}
CarBuilderInitializer.java
public interface CarBuilderInitializer {
Car.Builder initBuilder(Car.Builder builder);
}
YourCustomBuilderCar.java
public class YourCustomBuilderCar implements CarBuilderInitializer {
@Override
public initBuilder(@NotNull Car.Builder builder) {
return builder
.model("Audi");
.color(Color.RED);
}
}
Client code: Car car1 = new Car.Builder() .initFrom(new YourCustomBuilderCar()) .speed(200) .build();
Upvotes: 0
Reputation: 425
This is a common problem. Essentially, you need to expose a asBuilder()
. This instance method obtains a builder that is initialized with the values of a passed instance.
Car existingCar = Car.builder()......build() ;
//new car with same props as existing but the speed
Car existingCarButFaster = existingCar.asBuilder()
.speed(existingCar.speed * 2)
.build();
The implementation of asBuilder()
should be straightforward.
Also, you seem to be generating a lot of boilerplate. If hooking up the codebase with a code generator is not a problem for you, you can use the Lombok
library that is used by a vast majority of projects.
/**
* If true, generate an instance method to obtain a builder that is initialized with the values of this instance
* Legal only if {@code @Builder} is used on a constructor, on the type itself, or on a static method that returns
* an instance of the declaring type.
*
* @return Whether to generate a {@code toBuilder()} method.
*/
boolean toBuilder() default false;
Upvotes: 1
Reputation: 4044
Add a method initFrom(Car)
to your Builder
class, that calls all getters from the given Car
instance and initialises its parameters with the return values. Later invocations of the Builder
's methods may overwrite these values with the then correct values.
As next you can introduce a TemplateCar
class as a new parent class to Car
that allows to be build with an incomplete parameter set, and that has its own Builder
(of course, then you need to change the above mentioned method to initFrom(TemplateCar)
).
Is this what you meant when asking for a "Template"?
Upvotes: 0