I have a design and object structuring related question. Here is the problem statement:
- I have a Robot object which is suppose to traverse the ground on its own. It would be provided movement instructions and it must parse accordingly. For example sample input would be:
a. RotateRight|Move|RotateLeft|Move|Move|Move
Where move is a unit movement on a grid.
I did a very basic design in java. (Complete Code Pasted below)
package com.roverboy.entity;
import com.roverboy.states.RotateLeftState;
import com.roverboy.states.RotateRightState;
import com.roverboy.states.State;
public class Rover {
private Coordinate roverCoordinate;
private State roverState;
private State rotateRight;
private State rotateLeft;
private State move;
public Rover() {
this(0, 0, Compass.NORTH);
}
public Rover(int xCoordinate, int yCoordinate, String direction) {
roverCoordinate = new Coordinate(xCoordinate, yCoordinate, direction);
rotateRight = new RotateRightState(this);
rotateLeft = new RotateLeftState(this);
move = new MoveState(this);
}
public State getRoverState() {
return roverState;
}
public void setRoverState(State roverState) {
this.roverState = roverState;
}
public Coordinate currentCoordinates() {
return roverCoordinate;
}
public void rotateRight() {
roverState = rotateRight;
roverState.action();
}
public void rotateLeft() {
roverState = rotateLeft;
roverState.action();
}
public void move() {
roverState = move;
roverState.action();
}
}
package com.roverboy.states;
public interface State {
public void action();
}
package com.roverboy.entity;
import com.roverboy.states.State;
public class MoveState implements State {
private Rover rover;
public MoveState(Rover rover) {
this.rover = rover;
}
public void action() {
rover.currentCoordinates().setXCoordinate(
(Compass.EAST).equalsIgnoreCase(rover.currentCoordinates()
.getFacingDirection()) ? rover.currentCoordinates()
.getXCoordinate() + 1 : rover.currentCoordinates()
.getXCoordinate());
rover.currentCoordinates().setXCoordinate(
(Compass.WEST).equalsIgnoreCase(rover.currentCoordinates()
.getFacingDirection()) ? rover.currentCoordinates()
.getXCoordinate() - 1 : rover.currentCoordinates()
.getXCoordinate());
rover.currentCoordinates().setYCoordinate(
(Compass.NORTH).equalsIgnoreCase(rover.currentCoordinates()
.getFacingDirection()) ? rover.currentCoordinates()
.getYCoordinate() + 1 : rover.currentCoordinates()
.getYCoordinate());
rover.currentCoordinates().setYCoordinate(
(Compass.SOUTH).equalsIgnoreCase(rover.currentCoordinates()
.getFacingDirection()) ? rover.currentCoordinates()
.getYCoordinate() - 1 : rover.currentCoordinates()
.getYCoordinate());
}
}
package com.roverboy.states;
import com.roverboy.entity.Rover;
public class RotateRightState implements State {
private Rover rover;
public RotateRightState(Rover rover) {
this.rover = rover;
}
public void action() {
rover.currentCoordinates().directionOnRight();
}
}
package com.roverboy.states;
import com.roverboy.entity.Rover;
public class RotateLeftState implements State {
private Rover rover;
public RotateLeftState(Rover rover)
{
this.rover = rover;
}
public void action() {
rover.currentCoordinates().directionOnLeft();
}
}
package com.roverboy.entity;
public class Coordinate {
private int xCoordinate;
private int yCoordinate;
private Direction direction;
{
Direction north = new Direction(Compass.NORTH);
Direction south = new Direction(Compass.SOUTH);
Direction east = new Direction(Compass.EAST);
Direction west = new Direction(Compass.WEST);
north.directionOnRight = east;
north.directionOnLeft = west;
east.directionOnRight = north;
east.directionOnLeft = south;
south.directionOnRight = west;
south.directionOnLeft = east;
west.directionOnRight = south;
west.directionOnLeft = north;
direction = north;
}
public Coordinate(int xCoordinate, int yCoordinate, String direction) {
this.xCoordinate = xCoordinate;
this.yCoordinate = yCoordinate;
this.direction.face(direction);
}
public int getXCoordinate() {
return xCoordinate;
}
public void setXCoordinate(int coordinate) {
xCoordinate = coordinate;
}
public int getYCoordinate() {
return yCoordinate;
}
public void setYCoordinate(int coordinate) {
yCoordinate = coordinate;
}
public void directionOnRight()
{
direction.directionOnRight();
}
public void directionOnLeft()
{
direction.directionOnLeft();
}
public String getFacingDirection()
{
return direction.directionValue;
}
}
class Direction
{
String directionValue;
Direction directionOnRight;
Direction directionOnLeft;
Direction(String directionValue)
{
this.directionValue = directionValue;
}
void face(String directionValue)
{
for(int i=0;i<4;i++)
{
if(this.directionValue.equalsIgnoreCase(directionValue))
break;
else
directionOnRight();
}
}
void directionOnRight()
{
directionValue = directionOnRight.directionValue;
directionOnRight = directionOnRight.directionOnRight;
directionOnLeft = directionOnRight.directionOnLeft;
}
void directionOnLeft()
{
directionValue = directionOnLeft.directionValue;
directionOnRight = directionOnLeft.directionOnRight;
directionOnLeft = directionOnLeft.directionOnLeft;
}
}
Now my doubt is with this last class “Direction” and “Coordinate”. coordinate represents a coordinate object for rover which helps it maintain its direction. Currently to keep track of direction I am using a doubly linked list of Direction objects, which pretty much work like a compass. Rotate left or right.
Here are the questions that I have.
1. I have used state pattern and shown design for direction tracking. Is there a better approach to simplify even this? Rem. I need to maintain coordinates correctly; such that if you move towards +y axis, my coordinates should be in + else in minus. Same for X axis.
-
Currently the responsibility for changing the face of the rover is indirectly delegated to coordinates and to direction class. Is this really correct? Isn’t rover responsible for maintaining direction? Am I really right in my design to delegate that responsibility down to coordinate and direction class; just because it is easier to manipulate it there?
-
Any simple design improvements and suggestions on code will be most welcome. Feel free to critique.
Thanks for your patience and feedback; in advance.
You’re asking for how to simplify. If I may suggest something bold, why not use an opaque int for direction and have a static class to deal with it? By “opaque int” I mean that your code would never use it directly, but only as argument to the Direction class.
Here’s some partial java-styled pseudocode to show what I mean.
This way of doing things would have the advantage of using fewer allocations, so the garbage collector won’t need to run as often. Another advantage is that the design remains object-oriented in the sense that you can easily change the direction class if later you want to be able to rotate by e.g. 45 degrees at a time.
To answer your other questions, I think it’s perfectly fine to delegate to the Direction class the task of changing a coordinate along a certain direction. The rover would be responsible for maintaining direction only in the sense that the rover object would contain an int field to store the direction it’s facing.