QuickBuilder
Builders without boiler plate.
Rationale
The builder pattern helps keep tests readable and maintainable but requires tedious boilerplate. QuickBuilder lets you spend more time on your real code, and less writing boilerplate, by generating fully featured builders on the fly.
You supply an interface, QuickBuilder supplies an implementation.
QuickBuilder can create builders for pretty much any class - it cleanly handles immutable types and other classes not following the bean convention without resorting to dirty tricks like setting fields via reflection. Only the public interface of your class is used.
QuickBuilder is not a code generator. Builders are generated at runtime, there's no autogenerated code for you to maintain.
Download
It's on maven central
<dependency>
<groupId>org.pitest.quickbuilder</groupId>
<artifactId>quickbuilder</artifactId>
<version>1.2</version>
</dependency>
60 second quickstart
For any bean e.g.
public class Person {
private String name;
private int age;
private Person partner;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setPartner(Person partner) {
this.partner = partner;
}
// etc
}
Create a builder by declaring an interface
interface PersonBuilder extends Builder<Person> {
PersonBuilder withName(String name);
PersonBuilder withAge(int age);
PersonBuilder withPartner(Builder<Person> partner);
PersonBuilder withFriend(Person partner);
}
Get an instance by calling QB.builder, then use it like any other builder
Person p = QB.builder(PersonBuilder.class).withName("Bob").withAge(42).build();
For classes without a public no-args constructor you can supply a "seed" function that creates the instance.
class Seed implements Generator<PersonBuilder,Person> {
@Override
public Person generate(PersonBuilder builder) {
return Person.create();
}
}
// pass an instance of the seed function to QuickBuilder
PersonBuilder pb = QB.builder(PersonBuilder.class, new Seed());
For classes that don't provide setter methods (e.g immutable classes) you can tell QuickBuilder that you will take responsibility for a property by declaring an underscore method in your builder.
interface PersonBuilder extends Builder<Person> {
PersonBuilder withName(String name);
PersonBuilder withAge(int age);
PersonBuilder withPartner(PersonBuilder partner);
// underscore method telling QuickBuilder not to handle this property
String _Name();
}
The underscore method can then be used in your seed function to route the value to the right place.
class Seed implements Generator<Person, PersonBuilder> {
@Override
public Person generate(PersonBuilder builder) {
return Person.create(builder._Name());
}
}
You can of course choose to supply a seed function even when QuickBuilder does not require it and thereby move some some errors to being compile time errors rather than runtime.
When a seed function is used to set all properties the only types of runtime error that can occur are if :-
- The name of an underscore method does not match with a property declared on the builder
- The type of an underscore method does not match the type of the corresponding property
Note that these are mistakes within the builder interface definition - these probelms cannot be introduced by making changes to the built class.
2 minute overview of features
In addition to being much easier to write, the builders that QuickBuilder creates are probably better behaved and more richly featured than the ones you might have been building by hand.
Fully immutable
QuickBuilder creates immutable builders - their internal state is never updated, instead they return updated shallow copies of themselves.
Builders may therefore be reused without unexpected side effects
interface PersonBuilder extends Builder<Person> {
// ... etc
}
PersonBuilder youngPerson = QB.builder(PersonBuilder.class).withAge(18);
Person rita = youngPerson.withName("Rita").withPartner(bob).build();
Person sue = youngPerson.withName("Sue").build();
// Rita and Sue do not share Bob
Respects default values
If you don't supply a value for a property QuickBuilder will not try to overwrite it.
Given the bean
public class Person {
private int age;
public Person() {
age = 18; // set a default
}
// etc
}
Common hand rolled builder implementations would overwrite the default age with primitive int default of 0 when no value was supplied, but calling
Person p = QB.builder(PersonBuilder).withName("Tom").build();
Will return a person called Tom with the default age of 18.
Sequences
Occasionally you may want to create a series of objects that differ from each other on one or more property. Allthough you could create these by calling with
methods generating objects from a sequence may result in more readable code.
QuickBuilder currently provides one simple sequence implementation that takes it's values from a list that you supply.
import static org.pitest.quickbuilder.common.ElementSequence.from;
import static java.util.Arrays.asList;
interface PersonBuilder extends SequenceBuilder<Person> {
// ... etc
}
PersonBuilder person = QB.builder(PersonBuilder.class)
.withName(from(asList("Paul", "Barry", "Sarah")))
.withAge(from(asList(20,29,42)))
Iterator<Person> people = person.iterator();
people.next(); // a person named Paul with age 20
people.next(); // a person named Barry with age 29
people.next(); // a person named Sarah with age 42
person.buildAll() // a list with Paul(20), Barry(29) and Sarah(42)
person.build(2) // a list with Paul(20) and Barry(29)
Notice that the SequenceBuilders are also immutable.
Features
- Automatically creates builders for beans - you just supply an interface
- Creates builders for non-beans (e.g. immutable classes) by defining interface and small seeder function
- Builder methods may take normal types or other builders as parameters
- Supports any lowercase prefix for property methods eg "withName", "andName", "usingName"
- Builders are immutable and thread safe
Design principles
- Well behaved - only public interface of built type used
- Non invasive - no changes or annotations needed on built type
- Fail as early as possible - error on type creation or compile if possible
TODO
- Separate interface for underscore methods
- Collect list entries individually
- Auto generation for classes with single factory method
- Auto generation for classes with constructor without repeated types
Alternatives
Other approaches you might want to consider
- https://code.google.com/p/make-it-easy/
- http://code.google.com/a/eclipselabs.org/p/bob-the-builder/
- http://projectlombok.org/api/lombok/experimental/Builder.html
Releases
1.2
- SequenceBuilder implement Iterable
- Report type mismatch of Builder parameter with methods
- Composing operations repeat, once, limit, theSame
- Convience builders integersFrom, nullValue
- Converting builders
1.1
- Builders immutable when generating sequences
1.0
Initial release