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.
We’ve provided our WorkerDrones
with a means to determine an appropriate method of transportation by inspecting any given RobotPart
implementation. Now WorkerDrones
may select a TransportationMechanism
implementation that suits each RobotPart
. But we have yet to implement the actual logic involved. This is what we’ll cover in this tutorial. Look at how eager the little guy is! Let’s not delay; he’s got plenty of work to do.
Once again, here is our narrative:
“Mechs with Big Guns” is a factory that produces large, robotic vehicles designed to shoot other large, robotic vehicles. Robots are composed of several robotic parts, delivered by suppliers. Parts are loaded into a delivery bay, and are transported by worker drones to various rooms; functional parts such as arms, legs, etc., are dispatched to an assembly room. Guns and explosives are dispatched to an armoury.
The factory hosts many worker drones to assemble the robots. These drones will specialise in the construction of 1 specific robot, and will require all parts that make up that robot in order to build it. Once the drone has acquired all parts, it will enter the assembly room and build the robot. Newly built robots are transported to the armoury where waiting drones outfit them with guns. From time to time, two robots will randomly be selected from the armoury, and will engage one another in the arena, an advanced testing-ground in the factory. The winning robot will be repaired in the arena by repair drones. Its design will be promoted on a leader board, tracking each design and their associated victories.
Let’s look at what exactly happens when we transport a RobotPart
.
First, the WorkerDrone
needs to identify the RobotPart
that it just picked up, so that it can transport the part to the correct FactoryRoom
. Let’s dive right in.
In the previous tutorial, we defined a means to do this by examining a RobotPart's
RobotPartCategory
and returning an appropriate TransportMechanism
. Now, let’s add logic to our TransportMechanism
.
First, we need to keep track of the FactoryRoom
where we’ll offload the RobotParts:
C#
private FactoryRoom _factoryRoom;
Java
private E _factoryRoom;
First of all, what can we tell about the difference between both implementations? Both contain private properties, but our C# implementation is explicitly bound to a FactoryRoom
object. Our Java implementation, on the other hand, seems to be bound to the letter “E”.
“What’s that all about?”
The difference in implementations can be explained by discussing Generics. Essentially, Generics allow us to define an action, like a method, without defining a concrete return-type or input parameter – instead, we define these in concrete implementations of our abstraction. At this point, rather than go off-topic, I’ll provide a link to a thorough tutorial on this subject in C#.
In a nutshell, the difference in implementations comes down to a personal preference – I prefer Java’s implementation of Generics over C#’s, specifically Java’s support for covariance and contravariance. Again, I’m happy to follow up with this offline, or to host a separate post on the subject, but for now, let’s keep going.
Let’s look at our Java implementation of transportMechanism
:
public abstract class transportMechanism<E extends factoryRoom, U extends robotPart> {
Here, we’re telling the compiler that our transportMechanism
class will require 2 concrete implementations, both of which should be derived from factoryRoom
and robotPart
respectively. To illustrate this, let’s look at armouryTransportMechanism
, a class derived from transportMechanism
in Java:
public class armouryTransportMechanism extends transportMechanism<armoury, weapon> { @Override public armoury getFactoryRoom() { return new armoury(); } }
Notice our Generic implementation of factoryRoom
and robotPart
map to armoury
and weapon
, respectfully.
I’ll cover more about Generics on request. For now, let’s co back to our design.
Our TransportMechanism
needs to return an appropriate FactoryRoom
:
C#
public abstract FactoryRoom GetFactoryRoom();
Java
public abstract E getFactoryRoom();
So, what actually happens when a WorkerDrone
moves RobotParts
to a FactoryRoom
? The WorkerDrone
needs to enter the FactoryRoom
, and then offload its components into the FactoryRoom
:
C#
public void EnterRoom() { _factoryRoom = GetFactoryRoom(); _factoryRoom.AddTransportationMechanism(this); } public FactoryRoom OffLoadRobotParts(List<RobotPart> robotParts) { if (_factoryRoom == null) { EnterRoom(); } _factoryRoom.SetRobotParts(new List<RobotPart>(robotParts)); robotParts.Clear(); return _factoryRoom; }
Java
public void enterRoom() { _factoryRoom = getFactoryRoom(); _factoryRoom.addTransportationMechanism(this); } public E offLoadRobotParts(List<U>robotParts) { if (_factoryRoom == null) { enterRoom(); } _factoryRoom.setRobotParts(new ArrayList<U>(robotParts)); robotParts.clear(); return _factoryRoom; }
Here is a breakdown of what’s happening:
Our TransportMechanism
returns a FactoryRoom
implementation, based on the RobotPart
carried by the WorkerDrone
, and then the FactoryRoom
adds the TransportationMechanism
to its list of occupants:
C#
public void AddTransportationMechanism(TransportMechanism transportMechanism) { _transportMechanisms.Add(transportMechanism); }
Java
public void addTransportationMechanism(transportMechanism transportMechanism) { _transportMechanisms.add(transportMechanism); }
OK. Now our WorkerDrone
has entered the FactoryRoom
. It should now offload its RobotParts
via the OffLoadRobotParts
method above. Here’s what’s happening:
- A safeguard is in place to ensure that the
WorkerDrone
enters the room before offloading components - The
WorkerDrones
RobotPart
payload is copied to theFactoryRoom
- The
WorkerDrones
RobotPart
payload is emptied
“Why the safeguard? Can’t we just explicitly call the
EnterRoom
method before callingOffLoadRobotParts
?”
Yes, but let’s offer another layer of protection for consuming applications. After all, if a developer forgot to ensure that a WorkerDrone
enters a room before offloading RobotParts
, the system would crash. Even if we implemented counter-measures to prevent this, our WorkerDrone
would effectively dump its payload somewhere in the Factory
.
Our WorkerDrone
is now housed within an appropriate FactoryRoom
, and has offloaded its RobotParts
to that FactoryRoom
.
“So how did we get here?”
Let’s examine the associated Unit Test:
C#
[Test] public void WorkerDroneOffLoadsRobotParts() { WorkerDrone workerDrone = new MockedWorkerDrone(); RobotPart robotPart = new MockedRobotPart(RobotPartCategory.Assembly); workerDrone.PickUpRobotPart(robotPart); var factoryRoom = workerDrone.TransportRobotParts(); Assert.AreEqual(0, workerDrone.GetRobotPartCount()); Assert.AreEqual(1, factoryRoom.GetRobotPartCount()); Assert.IsInstanceOf<AssemblyRoom>(factoryRoom); robotPart = new MockedRobotPart(RobotPartCategory.Weapon); workerDrone.PickUpRobotPart(robotPart); factoryRoom = workerDrone.TransportRobotParts(); Assert.AreEqual(0, workerDrone.GetRobotPartCount()); Assert.AreEqual(1, factoryRoom.GetRobotPartCount()); Assert.IsInstanceOf<Armoury>(factoryRoom); }
Java
@Test public void workerDroneOffLoadsRobotParts() { workerDrone workerDrone = new mockedWorkerDrone(); robotPart robotPart = new mockedRobotPart(robotPartCategory.assembly); workerDrone.pickUpRobotPart(robotPart); factoryRoom factoryRoom = workerDrone.transportRobotParts(); assertEquals(0, workerDrone.getRobotPartCount()); assertEquals(1, factoryRoom.getRobotPartCount()); assertThat(factoryRoom, instanceOf(assemblyRoom.class)); robotPart = new mockedRobotPart(robotPartCategory.weapon); workerDrone.pickUpRobotPart(robotPart); factoryRoom = workerDrone.transportRobotParts(); assertEquals(0, workerDrone.getRobotPartCount()); assertEquals(1, factoryRoom.getRobotPartCount()); assertThat(factoryRoom, instanceOf(armoury.class)); }
Notice out first pair of Asserts. We’ve transported the RobotParts
from WorkerDrone
to FactoryRoom
, and simply assert that both components contain the correct number of RobotParts
. Next, we assert that our TransportMechanism has selected the correct FactoryRoom
instance; Weapons
go to the Armoury
, Assemblies
to the AssemblyRoom
.
“Great. I just looked at those FactoryRoom and RobotPart implementations. They’re all implementations of abstractions. Why didn’t you use interfaces, instead of abstract classes?”
There are 2 reasons for this:
- Our abstractions contain methods that need to be accessed by the implementations
- Our implementations are instances of our abstractions from a real-world perspective – they don’t just exhibit a set of behaviours.
It’s worth noting that a class can derive from a single class only in both C# and Java, whereas a class can derive from many interfaces as you like.
The next tutorial in the series will focus on returning our WorkerDrones
to the DeliveryBay
, and outlining the structure of RobotBuilders
.
Connect with me: