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.
Comments
4 responses to “A Better Builder”
Nice. I like this approach. It provides a very elegant mechanism for mocking, for sure.
I’ve just completed a recent project involving a lot of “mapping” or “translating”. I opted for individual mapper classes due to their reusability across different scenarios – for example the mapping of a particular type that is a member of many different, and quite independent, objects.
In addition, the mapping classes were really pretty big and to have all of this logic in a single class would create a massive class. Not quite a monolithic block of code in a single method, but it would still have the feel of a retro procedural programming treat.
I guess for me I felt that the individual mapper classes were better aligned with the principles of single responsibility, separation of concerns, increased cohesion and reduced coupling, albeit for the cost of class explosion! Each mapper only knew about the input object, the resulting output object, and any child or member objects. It’s also simple to test – something which I did actually do.
Like everything though, it depends on the situation and it’s interesting to have another tool in the bat belt for this. I’ll definitely take this into consideration next time around.
[…] October 14, 2009 grwilliamson Leave a comment Go to comments Jakub has an interesting slant on the problem of mapping objects from one representation into another, without […]
Hi Jakub
I had another thought…
Couldn’t the same swappable behaviour be achieved by simply extending the builder with a mock builder and overriding the methods you don’t want to test? I.e. use the flexibility that say the template method pattern affords?
If by extending with a mock, you mean extend the class under test and stub out methods, then yeah you could certainly do that, but it’s not nice. The advantage that dynamic mock frameworks like easymock give you in this instance is brevity of code and flexibility.
expect(mockBuilder.buildDoor(otherDoorData)).andReturn(new Door());
expect(mockBuilder.buildWindow(otherWindowData)).andThrow(new NumberFormatException());
is much easier to deal with than the following repeated a dozen times in your unit tests:
builder = new HouseBuilder() {
@Override
Window buildDoor(OtherDoorType otherDoorData) {
return new Door();
}
@Override
Window buildWindow(OtherWindowType otherWindowData) {
throw new NumberFormatException();
}
}
In fact the latter approach may not be possible if your builder class is final. You may also need to call a method more than once and have it return different values:
expect(mockBuilder.buildDoor(otherDoorData)).andReturn(new Door());
expect(mockBuilder.buildDoor(moreDoorData)).andThrow(new IllegalStateException(“You already have a door”));