Download the code in C#
Download the code in Java
Check out my interview on .NET Rocks! – TDD on .NET and Java with Paul Mooney
For a brief overview, please refer to this post.
Overview
In the last tutorial we focused on correcting some logic in our classes and tests. It’s about time that we started building Robots. Let’s start with a simple example consisting of the following components:
- Head
- Torso
- 2x Arms
- 2x Legs
Not the most exciting contraption, but we can expand on this later. For now, let’s define these simple properties and combine them in a simple class called Robot:
C#
public abstract class Robot { public Head Head { get; set; } public Torso Torso { get; set; } public Arm LeftArm { get; set; } public Arm RightArm { get; set; } public Leg LeftLeg { get; set; } public Leg RightLeg { get; set; } }
Java
public abstract class robot { private head _head; private torso _torso; private arm _leftArm; private arm _rightArm; private leg _leftLeg; private leg _rightLeg; public head getHead() { return _head; } public void setHead(head head) { _head = head; } public torso getTorso() { return _torso; } public void setTorso(torso torso) { _torso = torso; } public arm getLeftArm() { return _leftArm; } public void setLeftArm(arm leftArm) { _leftArm = leftArm; } public arm getRightArm() { return _rightArm; } public void setRightArm(arm rightArm) { _rightArm = rightArm; } public leg getleftLeg() { return _leftLeg; } public void setLeftLeg(leg leftLeg) { _leftLeg = leftLeg; } public leg getRightLeg() { return _rightLeg; } public void setRightLeg(leg rightLeg) { _rightLeg = rightLeg; } }
“Wait! Why are you writing implementation-specific code? This is about TDD! Where are your unit tests?”
If I write a class as above, I can expect that it will work because it’s essentially a template, or placeholder for data. There is no logic, and very little, if any scope for error. I could write unit tests for this class, but what would they prove? There is nothing specific to my application, in terms of logic. Any associated unit tests would simply test the JVM (Java) or CLR (.NET), and would therefore be superfluous.
Disclaimer: A key factor in mastering either OOD or TDD is knowing when not to use them.
Let’s start building Robots. Robots are complicated structures composed of several key components. Our application might grow to support multiple variants of Robot. Imagine an application that featured thousands of Robots. Assembling each Robot to a unique specification would be a cumbersome task. Ultimately, the application would become bloated with Robot bootstrapper code, and would quickly become unmanageable.
“Sounds like a maintenance nightmare. What can we do about it?”
Ideally we would have a component that created each Robot for us, with minimal effort. Fortunately, from a design perspective, a suitable pattern exists.
Introducing the Builder Pattern
The Builder pattern provides a means to encapsulate the means by which an object is constructed. It also allows us to modify the construction process to allow for multiple implementations; in our case, to create multiple variants of Robot. In plain English, this means that an application the leverages a builder component does not need to know anything about the object being constructed.
“That sounds great, but isn’t the Builder pattern really just about good house-keeping? All we really achieve here is separation-of-concerns, which is fine, but my application is simple. I just need a few robots; I can assemble these with a few lines of code.”
The Builder pattern is about providing an object-building schematic. Let’s go through the code:
C#
public abstract class RobotBuilder { protected Robot robot; public Robot Robot { get { return robot; } } public abstract void BuildHead(); public abstract void BuildTorso(); public abstract void BuildArms(); public abstract void BuildLegs(); }
Java
public abstract class robotBuilder { protected robot robot; public robot getRobot() { return robot; } public abstract void buildHead(); public abstract void buildTorso(); public abstract void buildArms(); public abstract void buildLegs(); }
This abstraction is the core of our Builder implementation. Notice that it provides a list of methods necessary to construct a Robot. Here is a simple implementation that builds a basic Robot:
C#
public class BasicRobotBuilder : RobotBuilder { public BasicRobotBuilder() { robot = new BasicRobot(); } public override void BuildHead() { robot.Head = new BasicHead(); } public override void BuildTorso() { robot.Torso = new BasicTorso(); } public override void BuildArms() { robot.LeftArm = new BasicLeftArm(); robot.RightArm = new BasicRightArm(); } public override void BuildLegs() { robot.LeftLeg = new BasicLeftLeg(); robot.RightLeg = new BasicRightLeg(); } }
Java
public class basicRobotBuilder extends robotBuilder { public basicRobotBuilder() { robot = new basicRobot(); } @Override public void buildHead() { robot.setHead(new basicHead()); } @Override public void buildTorso() { robot.setTorso(new basicTorso()); } @Override public void buildArms() { robot.setLeftArm(new basicLeftArm()); robot.setRightArm(new basicRightArm()); } @Override public void buildLegs() { robot.setLeftLeg(new basicLeftLeg()); robot.setRightLeg(new basicRightLeg()); } }
It’s not your application’s job to build robots. It’s your application’s job to manage those robots at runtime. The application should be agnostic in terms of how robots are provided. Let’s add another Robot to our application; this time, let’s design the Robot to run on caterpillars, rather than legs.
First, we introduce a new class called Caterpillar. Caterpillar must extend Leg, so that it’s compatible with our Robot and RobotBuilder abstractions.
C#
public class Caterpillar : Leg {}
Java
public class caterpillar extends leg { }
This class doesn’t do anything right now. We’ll implement behaviour in the next tutorial. For now, let’s provide a means to build our CaterpillarRobot.
C#
public class CaterpillarRobotBuilder : RobotBuilder { public CaterpillarRobotBuilder() { robot = new CaterpillarRobot(); } public override void BuildHead() { robot.Head = new BasicHead(); } public override void BuildTorso() { robot.Torso = new BasicTorso(); } public override void BuildArms() { robot.LeftArm = new BasicLeftArm(); robot.RightArm = new BasicRightArm(); } public override void BuildLegs() { robot.LeftLeg = new Caterpillar(); robot.RightLeg = new Caterpillar(); } }
Java
public class caterpillarRobotBuilder extends robotBuilder { public caterpillarRobotBuilder() { robot = new caterpillarRobot(); } @Override public void buildHead() { robot.setHead(new basicHead()); } @Override public void buildTorso() { robot.setTorso(new basicTorso()); } @Override public void buildArms() { robot.setLeftArm(new basicLeftArm()); robot.setRightArm(new basicRightArm()); } @Override public void buildLegs() { robot.setLeftLeg(new caterpillar()); robot.setRightLeg(new caterpillar()); } }
Notice that all methods remain the same, with the exception of BuildLegs, which now attaches Caterpillar objects to both left and right legs. We create an instance of our CaterpillarRobot as follows:
C#
var caterpillarRobotBuilder = new CaterpillarRobotBuilder(); caterpillarRobotBuilder.BuildHead(); caterpillarRobotBuilder.BuildTorso(); caterpillarRobotBuilder.BuildArms(); caterpillarRobotBuilder.BuildLegs();
Java
caterpillarRobotBuilder caterpillarRobotBuilder = new caterpillarRobotBuilder(); caterpillarRobotBuilder.buildHead(); caterpillarRobotBuilder.buildTorso(); caterpillarRobotBuilder.buildArms(); caterpillarRobotBuilder.buildLegs();
“That’s still a lot of repetitive code. Your CaterpillarRobot isn’t that much different from your BasicRobot. Why not just extend CaterpillarRobotBuilder from BasicRobotBuilder?”
Yes, both classes are similar. Here, you must use your best Object Oriented judgement. If your classes are unlikely to change, then yes, extending BasicRobotBuilder to CaterpillarRobotBuilder might be a worthwhile strategy. However, you must consider the cost of doing this, should future requirements change. Suppose that we introduce a fundamental change to our CaterpillarRobot class, such that it no longer resembles, nor behaves in the same manner as a BasicRobot. In that case, we would have to extract the CaterpillarRobotBuilder class from BasicRobotBuilder, and extend if from RobotBuilder, which may involve significant effort.
As regards repetitive code, let’s look at a means of encapsulating this further, in what’s called a Director. The Director’s purpose is to invoke the Builder’s methods to facilitate object construction, encapsulating construction logic, and removing the need to implement build methods explicitly:
C#
public class RobotConstructor { public void Construct(RobotBuilder robotBuilder) { robotBuilder.BuildHead(); robotBuilder.BuildTorso(); robotBuilder.BuildArms(); robotBuilder.BuildLegs(); } }
Java
public void Construct(robotBuilder robotBuilder) { robotBuilder.buildHead(); robotBuilder.buildTorso(); robotBuilder.buildArms(); robotBuilder.buildLegs(); }
Now our build logic is encapsulated within a controlling class, which is agnostic in terms of the actual implementation of robotbuilder – we can load any implementation we like, and our constructor will just build it.
C#
var robotConstructor = new RobotConstructor(); var basicRobotBuilder = new BasicRobotBuilder(); robotConstructor.Construct(basicRobotBuilder);
Java
robotConstructor robotConstructor = new robotConstructor(); basicRobotBuilder basicRobotBuilder = new basicRobotBuilder(); robotConstructor.Construct(basicRobotBuilder);
Summary
We’ve looked at the Builder pattern in this tutorial, and have found that it is an effective way of:
- Providing an abstraction that allows multiple robot types to be assembled in multiple configurations
- Encapsulates robot assembly logic
- Facilitates the instantiation of complex, composite objects
In the next tutorial in this series we’ll focus on making robots fight.
Connect with me: