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.
Following on from the previous tutorial, where we looked at Supplier
and DeliveryBay
objects, let’s move on to WorkerDrones
.
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.
Worker Drones
Worker Drones have several functions, as per the narrative, so we’re going to tackle the implementation one step at a time. It seems that at a fundamental level, the drones need to be able to differentiate between different RobotParts. For example, consider a situation in which the following set of robotic legs arrive in the DeliveryBay:
In that case, our WorkerDrone
needs to transport these to the AssemblyRoom
. However, check out the guns on this guy!!!
Those weapons, should they arrive in the DeliveryBay
, need to be transported to the Armoury
.
Essentially, WorkerDrone
behaviour depends on the category that each RobotPart
is associated with, and can change at runtime. Consider the following scenario:
WorkerDrone
entersDeliveryBay
and collects aRobotPart
- The
RobotPart
is identified as an item that belongs in theAssemblyRoom
WorkerDrone
transports theRobotPart
to theAssemblyRoom
WorkerDrone
picks up the nextRobotPart
, which happens to be aWeapon
WorkerDrone
delivers this to theArmoury
WorkerDrones
have 2 separate types of behaviour that govern RobotPart
transportation. This is commonly known as the Strategy Design Pattern.
“Wait, that actually sounds like a Bridge pattern to me!”
Structurally, they are both the same. The difference is in their implementation. A Bridge is a behaviour that is set at design-time, and remains constant. A Strategy on the other hand, can change at runtime.
What we’re essentially doing is abstracting WorkerDrone
transportation behaviour to separate classes that share the same abstraction. Our WorkerDrone
simply chooses the correct implementation to suit a given scenario, and the implementation does the work.
Let’s keep thiings simple for the moment; we won’t actually implement the act of transporting RobotParts, we’ll simply implement the fundamental components involved.
To start, we need to identify RobotParts
. So let’s tag them with a specific category – either Assembly
, or Weapon
. First, we need to define these categories:
C#
public enum RobotPartCategory { Assembly, Weapon }
Java
public enum robotPartCategory { assembly, weapon }
Now, we modify our RobotPart
astraction to accept either of these categories:
C#
public abstract class RobotPart { private readonly RobotPartCategory _robotPartCategory; public RobotPartCategory RobotPartCategory { get { return _robotPartCategory; } } protected RobotPart(RobotPartCategory robotPartCategory) { _robotPartCategory = robotPartCategory; } }
Java
public abstract class robotPart { protected robotPartCategory robotPartCategory; public robotPartCategory getRobotPartCategory() { return robotPartCategory; } protected robotPart(robotPartCategory robotPartCategory) { this.robotPartCategory = robotPartCategory; } }
So far, so good. We spoke about different transportation behaviours. So let’s first create the abstraction:
C#
public abstract class TransportMechanism {}
Java
public abstract class transportMechanism {}
And both implementations; Assembly and Weapon. Remember: we’re not implementing any logic in these classes yet – we first need to ensure that our WorkerDrone can apply the correct behaviour based on each RobotPart
:
C#
public class AssemblyRoomTransportMechanism : TransportMechanism {} public class ArmouryTransportMechanism : TransportMechanism {}
Java
public class assemblyRoomTransportMechanism extends transportMechanism {} public class armouryTransportMechanism extends transportMechanism {}
Now let’s implement a simple method that allows our WorkerDrone
to choose the manner in which it will transport a given RobotPart
, by first examining that RobotPart
C#
public abstract class WorkerDrone { public TransportMechanism TransportMechanism { get; private set; } public void IdentifyRobotPart(RobotPart robotPart) { switch (robotPart.RobotPartCategory) { case RobotPartCategory.Assembly: TransportMechanism = new AssemblyRoomTransportMechanism(); break; case RobotPartCategory.Weapon: TransportMechanism = new ArmouryTransportMechanism(); break; } } }
Java
public class workerDrone { private transportMechanism _transportMechanism; public transportMechanism getTransportMechanism() { return _transportMechanism; } public void identifyRobotPart(robotPart robotPart) { switch (robotPart.getRobotPartCategory()) { case assembly: _transportMechanism = new assemblyRoomTransportMechanism(); break; case weapon: _transportMechanism = new armouryTransportMechanism(); break; } } }
As usual, our WorkerDrone
is an abstraction and we apply a mocked implementation in our associated unit test:
C#
public void WorkerDroneIdentifiesRobotPart() { var robotPart = new MockedRobotPart(RobotPartCategory.Assembly); var workerDrone = new MockedWorkerDrone(); workerDrone.IdentifyRobotPart(robotPart); Assert.IsInstanceOf<AssemblyRoomTransportMechanism>(workerDrone.TransportMechanism); robotPart = new MockedRobotPart(RobotPartCategory.Weapon); workerDrone.IdentifyRobotPart(robotPart); Assert.IsInstanceOf<ArmouryTransportMechanism>(workerDrone.TransportMechanism); }
Java
public void workerDroneIdentifiesRobotPart() { robotPart robotPart = new mockedRobotPart(robotPartCategory.assembly); workerDrone workerDrone = new mockedWorkerDrone(); workerDrone.identifyRobotPart(robotPart); assertThat(workerDrone.getTransportMechanism(), instanceOf(assemblyRoomTransportMechanism.class)); robotPart = new mockedRobotPart(robotPartCategory.weapon); workerDrone.identifyRobotPart(robotPart); assertThat(workerDrone.getTransportMechanism(), instanceOf(armouryTransportMechanism.class)); }
Let’s walk through this test:
- We instnantiate a new
RobotPart
as an Assembly - We instatiate our
WorkerDrone
- The
WorkerDrone
examines theRobotPart
and instantiates itsTransportationMechanism
appropriately - We assert that the correct
TransportationMechanism
behavioural implementation has been applied for the AssemblyRobotPart
- We repeat the process for a Weapon
RobotPart
In the next tutorial, we’ll implement the logic involved in transporting RobotParts
from the DeliveryRoom
to both AssemblyRoom
and Armoury
.
Connect with me:
This tutorial is brief and concise which makes it cool enough for learning… I love it. But don’t you think the use of interface to define method signature as well as use of object polymorphism instead of case statement will be better for test driven designs. Just suggesting though…
Thank you! I’ll be covering interfaces in the next tutorial. The current implementation, specifically the switch statement to which you referred, can only be replaced with an abstraction if the underlying pattern were a Bridge, and its behaviour set at design time.
In our case, we need to explicitly choose the Transportation implementation by examining the RobotPart each time. I suppose that we could achieve this by loading each RobotPart with its associated TransportationMechanism, though this would cause overlapping concerns – a RobotPart shouldn’t know anything about how it should be transported.
Very good point though, which I will cover in future posts.
Reblogged this on Dinesh Ram Kali..