Jakub Korab
Tech, Opinion, and Doing Stuff

A Better Builder

October 13th, 2009

The builder pattern should be familiar to anyone who has needed to change data from one format to another. Often called assemblers, translators or similar, they’re found peppered throughout almost every project. The idea is pretty simple:

public class HouseBuilder {
    public House buildHouse(OtherHouseType otherHouse) {
        House house = new House();
        // copy a whole bunch of properties from the otherHouse....
        for (OtherWallType otherWall : otherHouse.getWalls()) {
            house.addWall(buildWall(otherWall));
        }
        return house;
    }

    private Wall buildWall(OtherWallType otherWall) {
        Wall wall = new Wall();
        // copy another whole bunch of other properties....
        // do something fancy every third Tuesday of the month...
        for (OtherWindowType otherWindow : otherWall.getWindows()) {
            wall.addWindow(buildWindow(otherWindow));
        }
        return wall;
    }

    private Window buildWindow(OtherWindowType otherWindow) {
        Window window = new Window();
        // you get the idea....
        return window;
    }
}

It’s OK for translating small object graphs, but for non-trivial work, these things can turn into huge heaving masses of code. Because they’re pretty quick to knock out, they’re generally untested as the above code doesn’t really lend itself to it. Take the buildWall() method above – you can’t really exercise the “every third Tuesday” case easily without running through buildHouse(), and then calling buildWindow(). It becomes a pain to construct the data model to pump into test cases, and as we know, anything that’s not easy or highly visible doesn’t get tested. Did I just say that? Oops. Sorry, industry secret.

One strategy that’s applied is to write builders for each object type, i.e. HouseBuilder, WallBuilder, WindowBuilder. These are then either wired together via dependency injection (ugly as it exposes internal mechanics), or hard-coded in with a setter to substitute a dependency at test time. There’s also the drawback of a morass of tiny classes proliferating in a package somewhere. Not necessarily a bad thing, but when doing this sort of work there’s typically a good amount of refactoring, as well as use of utility classes to enrich data when moving between formats. Personally, not a big fan of this approach.

So, here’s a cool little trick:

public class MuchImprovedHouseBuilder {
    private MuchImprovedHouseBuilder delegate;

    public MuchImprovedHouseBuilder() {
        delegate = this;
    }

    void substituteInternalBuilder(MuchImprovedHouseBuilder otherBuilder) {
        delegate = otherBuilder;
    }

    public House buildHouse(OtherHouseType otherHouse) {
        House house = new House();
        // copy a whole bunch of properties from the otherHouse....
        for (OtherWallType otherWall : otherHouse.getWalls()) {
            house.addWall(delegate.buildWall(otherWall));
        }
        return house;
    }

    Wall buildWall(OtherWallType otherWall) {
        Wall wall = new Wall();
        // copy another whole bunch of other properties....
        // do something fancy every third Tuesday of the month...
        for (OtherWindowType otherWindow : otherWall.getWindows()) {
            wall.addWindow(delegate.buildWindow(otherWindow));
        }
        return wall;
    }

    Window buildWindow(OtherWindowType otherWindow) {
        Window window = new Window();
        // you get the idea....
        return window;
    }
}

All of the translation is done in one builder class. On construction, a private field defines a “delegate”. Think of it as a placeholder that will do the “other bits” of the translation and set it to “this”. A package scoped setter allows a test to overwrite the builder with a mock. So with a bit of reference fiddling, we can then test one method at a time, without having to worry about the implementation details of the other methods in the class. All of the non-public methods are given package scope, so they can be tested straight out of your favourite test framework. So, you can now do this in live code:

HouseBuilder builder = new HouseBuilder();
House house = builder.build(otherHouseType);

And at the same time write tests like this:

HouseBuilder builder = new HouseBuilder();
HouseBuilder mockBuilder = EasyMock.createMock(HouseBuilder.class); // the classextension version of EasyMock
EasyMock.expect(mockBuilder.buildWindow(isA(OtherWindowType.class)).andReturn(new Window());
EasyMock.replay(mockBuilder);
builder.substituteInternalBuilder(mockBuilder);
Wall wall = builder.buildWall(otherWallType);

Simple, and you don’t have to stick in any new tools to achieve it.

Bob's girlfriend misunderstood when he said his diet needed more iron.


Filed under: java, testing | No Tag
No Tag
October 13th, 2009 22:12:24