Question

I've looked at various definitions of the builder pattern and whilst there's varying definitions, they tend to be focused on the broad definition of incremental construction. However, it seems that most of the examples of builders I've ever seen essentially just construct "value" types with method chaining.

I've found, however, that it can occasionally be more useful to use a completely different sort of model in the builder to accomplish additional tasks. So, in addition to what people generally expect from a builder, this builder:

  1. Can hide details of the type being created
  2. Embedding complex build logic into the builder
  3. Using the same builder to create multiple similar types

I've put together a simple example of a builder variant that I created in Java that'll hopefully better demonstrate the ideas. (Note: 1 isn't really well demonstrated since it would take a lot more code to illustrate)

class FamiliesBuilder {
    final Map<String, List<String>> familyMap = new HashMap<>();
    String lastName;
    List<String> familyList;

    FamiliesBuilder newFamily(String lastName) {
        this.familyList = this.familyMap.getOrDefault(lastName, new LinkedList<>());
        this.familyMap.put(lastName, this.familyList);
        this.lastName = lastName;
        return this;
    }

    FamiliesBuilder addFamilyMember(String firstName) {
        this.familyList.add(firstName);
        return this;
    }

    Map<String, List<Person>> buildLastNameLookup() {
        final Map<String, List<Person>> res = new HashMap<>();
        for (Entry<String, List<String>> e : this.familyMap.entrySet()) {
            final String personLastName = e.getKey();
            final List<Person> people = new LinkedList<>();
            for (String firstName : e.getValue()) {
                people.add(new Person(firstName, personLastName));
            }

            res.put(e.getKey(), people);
        }

        return res;
    }

    List<Person> build() {
        List<Person> people = new LinkedList<>();
        for (Entry<String, List<String>> e : this.familyMap.entrySet()) {
            String personLastName = e.getKey();
            for (String personFirstName : e.getValue()) {
                people.add(new Person(personFirstName, personLastName));
            }
        }

        return people;
    }

    class Person {
        private String firstName;
        private String lastName;

        private Person(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }
    }

    public static void main(String... args) {
        // Usage example
        List<Person> people = new FamiliesBuilder()
                .newFamily("Doe")
                .addFamilyMember("John")
                .addFamilyMember("Jane")
                .newFamily("Smith")
                .addFamilyMember("Tom")
                .build();
    }
}

I'm concerned that this variant is too far from the classic builder pattern that calling it a builder may be confusing and, if so, I'm wondering if there are any recognizable design patterns or techniques that accomplish a similar task.

Was it helpful?

Solution

Short answer: I like it, and I make builders with the same characteristics. I don't think this is an anti-pattern at all.

Details:

Can hide details of the type being created

If the details are unimportant, confusing or error-prone for the user of the type, then absolutely hide them in the builder. The builder is the expert so that the consumer of the type doesn't have to be.

Embedding complex build logic into the builder

Is the complexity fundamental to the type, because the "business domain" of the code is itself complex? Then absolutely, encapsulate the complexity somewhere. Whether it is in the builder or the type being built, doesn't really matter to me but it should go somewhere.

Using the same builder to create multiple similar types

This is your weakest point. Code re-use is overrated and generality has many costs. Often it is cheaper and easier to write two specific solutions than to write one general solution and specialize it two ways.

OTHER TIPS

I don't think the example you provided is the Builder pattern at all.

The Builder pattern is a way of creating an immutable object that has many optional parameters. The main reason for it is so that you don't need to have a ridiculous number of constructors (or factory methods) due to all the possible combinations. (It also provides the benefit of method chaining which can make the code easier to read).

If the objects are not immutable the pattern serves no real purpose as the object can be created and have the desired parameter set later, either through direct access to the field or through setter methods.

In the example you gave you are really just wrapping around a List<Person> object and providing functionality to add to it as well as providing a way to get a HashMap>.

In this instance a better way of doing this would be:

public class Family {
    private String lastName;
    private String[] firstNames;

    //alternatively there can be a List<Person> field instead of lastName and firstNames

    public Family(String lastName, String firstNames){
        //code omitted...        
    }

    public static HashMap<String, List<Person>> getLastNameLookup<Collection<Family>> families) {
        //code omitted...
    }
}

The code could then be used like this:

List<Family> families = new ArrayList<>();
families.add(new Family("Doe", new String[] { "John", "Jane" });
families.add(new Family("Smith", new String[] { "John" });

HashMap<String, List<Person>> lastNameLookup = Family.getLastNameLookup(families);

In summary, I do think it is an anti-pattern because the added complexity doesn't really provide any value. There is no information being hidden here that couldn't be hidden just as well in the class itself. I think the Builder pattern should only be used to create immutable objects that can have many optional values as a way to prevent needing to introduce a very large number of constructors or factory methods.

Licensed under: CC-BY-SA with attribution
scroll top