I want to create a class with a fluent builder, both of which can be inherited and extended. Base class should have all the common and mandatory fields, children should have different optional fields
simple example below (best, simplistic usecase I could come up with ;p)
base: Animal
name
age
static Builder
impl: Snake extends Animal
length
static Builder extends Animal.Builder
impl: Spider extends Animal
numberOfLegs
static Builder extends Animal.Builder
and I'd like to use it in one of those ways (most preferred one is the first one):
Spider elvis = Spider.name("elvis").age(1).numberOfLegs(8).build();
Spider elvis = Spider.builder().name("elvis").age(1).numberOfLegs(8).build();
Spider elvis = new Spider.Builder().name("elvis").age(1).numberOfLegs(8).build();
so far I failed and I'd be very grateful if you could please help me find a way out of it :) or maybe there is just a different approach that I should think about?
http://blog.crisp.se/2013/10/09/perlundholm/another-builder-pattern-for-java
http://egalluzzo.blogspot.com/2010/06/using-inheritance-with-fluent.html
Generic fluent Builder in Java
the code so far can be found below. there are some traces of the things I tried and failed, there are some unused or just weird stuff (best example is IBuildImpl). Those are left to give you an understanding of what I tried, but if you think that this needs moderation - please let me know and I'll clean them up
package fafafa;
public abstract class Animal<T> {
String name; //mandatory field, one of many
Integer age; //mandatory field, one of many
public String getName() {
return name;
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
interface IName {
IAge name(String name);
}
interface IAge {
IBuild age(Integer age);
}
interface IBuild<T extends Animal<T>> {
T build();
}
public abstract static class Builder<T extends Animal<T>, B extends Builder<T, B>>
implements IName, IAge, IBuild<T> {
protected T objectBeingBuilt;
protected abstract B that();
protected abstract T createEmptyObject();
Builder(){
this.objectBeingBuilt = createEmptyObject();
System.out.println();
}
@Override
public IAge name(String name) {
objectBeingBuilt.name = name;
return that();
}
@Override
public IBuild age(Integer age) {
objectBeingBuilt.age = age;
return that();
}
// @Override
// public T build() {
// return objectBeingBuilt;
// }
}
}
package fafafa;
public class Spider extends Animal<Spider> {
Integer numberOfLegs; //optional field, one of many
private Spider() {
}
public Integer getNumberOfLegs() {
return numberOfLegs;
}
@Override
public String toString() {
return "Spider{" +
"numberOfLegs='" + numberOfLegs + '\'' +
"} " + super.toString();
}
// public static Builder<Spider, Builder> name(String name) {
// return (Builder) new Builder().name(name);
// }
interface INumberOfLegs {
IBuild numberOfLegs(Integer numberOfLegs);
}
interface IBuildImpl extends IBuild<Spider>, INumberOfLegs {
@Override
Spider build();
}
public static class Builder extends Animal.Builder<Spider, Builder> implements IBuildImpl {
@Override
protected Builder that() {
return this;
}
@Override
protected Spider createEmptyObject() {
return new Spider();
}
public IBuild numberOfLegs(Integer numberOfLegs) {
objectBeingBuilt.numberOfLegs = numberOfLegs;
return that();
}
public Spider build() {
return objectBeingBuilt;
}
}
}
package fafafa;
public class Main {
public static void main(String[] args) {
Spider build = new Spider.Builder().name("elvis")
.age(1)
.numberOfLegs(8) //cannot resolve method numberOfLegs
.build();
System.out.println(build);
}
}
The problem of your code is the interface:
interface IAge {
IBuild age(Integer age);
}
This will always return the basic IBuild
interface with no parameter, no matter, if the implementation implements it with some argument. Actually even returning it with the parameter wouldn't extend the builder with additional methods.
Here is a suggestion:
1. Don't use IName
interface. Replace it with static entry method of the builder
2. Parametrize IAge
interface
3. No common builder needed. It can be replaced with inline lambda implementation
Here is the code:
@FunctionalInterface
public interface IAge<B> {
B age(Integer age);
}
public class AnimalBuilder implements IBuild<Animal> {
private final String name;
private final Integer age;
private Integer numberOfLegs;
private AnimalBuilder(String name, Integer age) {
this.name = name;
this.age = age;
}
// Builder entry method
public static IAge<AnimalBuilder> name(String name) {
return age -> new AnimalBuilder(name, age);
}
public AnimalBuilder numberOfLegs(int value) {
numberOfLegs = value;
return this;
}
@Override
public Animal build() {
return new Animal(name, age, numberOfLegs);
}
}
This allows following usage:
AnimalBuilder.name("elvis").age(1).numberOfLegs(8).build();